示例#1
0
  /**
   * Return whether autoburning will result in a unification.
   *
   * @param destType The destination type to unify with
   * @param burnEntity on which to attempt "autoburning"
   * @param info the info to use
   * @return AutoburnLogic.AutoburnInfo autoburnable result
   */
  public static AutoburnInfo getAutoburnInfo(
      TypeExpr destType, GemEntity burnEntity, TypeCheckInfo info) {

    // see if we have to do anything
    TypeExpr entityType;
    if (burnEntity == null || (entityType = burnEntity.getTypeExpr()) == null) {
      return AutoburnInfo.makeNoUnificationPossibleAutoburnInfo();
    }

    return getAutoburnInfoWorker(destType, entityType.getTypePieces(), null, info, null);
  }
  @Override
  public void commitValue() {

    IntellicutListEntry listEntry =
        (IntellicutListEntry) intellicutPanel.getIntellicutList().getSelectedValue();

    if (listEntry != null) {

      GemEntity gemEntity = (GemEntity) listEntry.getData();
      ModuleTypeInfo currentModuleInfo =
          valueEditorManager.getPerspective().getWorkingModuleTypeInfo();
      TypeExpr valueNodeType = getValueNode().getTypeExpr();
      TypeExpr functionType = gemEntity.getTypeExpr();
      TypeExpr unifiedType;

      try {
        unifiedType = TypeExpr.unify(valueNodeType, functionType, currentModuleInfo);
      } catch (TypeException e) {
        throw new IllegalStateException(e.getMessage());
      }

      GemEntityValueNode newValueNode = new GemEntityValueNode(gemEntity, unifiedType);
      replaceValueNode(newValueNode, true);
    }

    super.commitValue();
  }
    /**
     * See if we can reasonably use the values cached in the target's argument parts. For now this
     * just means if we can use the cached panels without performing any type conversions of any
     * sort. Precondition: target and inputToTypeExprMap are set Postcondition: none.
     *
     * @return boolean true if it's ok to use the values cached in the target's arguments.
     */
    private boolean canUseCachedArguments() {
      List<Gem.PartInput> argParts = targetDisplayedGem.getTargetArguments();

      // Get the input types.
      TypeExpr[] targetPieces = new TypeExpr[argParts.size()];
      for (int i = 0; i < targetPieces.length; i++) {
        targetPieces[i] = argParts.get(i).getType();
      }

      TypeExpr[] typesFromCachedArgs = new TypeExpr[targetPieces.length];

      // Step through args.  i'th arg corresponds to the i'th piece.
      // Note that there may be more pieces than args if the output type is a function
      for (int i = 0; i < targetPieces.length; i++) {
        // Get this input, and any cached value
        Gem.PartInput sinkPart = argParts.get(i);
        ValueNode cachedVN = gemCutter.getTableTop().getCachedValue(sinkPart);

        // there may be no cached value for this argument
        if (cachedVN != null) {
          typesFromCachedArgs[i] = cachedVN.getTypeExpr();
        }
      }

      ModuleTypeInfo contextModuleTypeInfo = gemCutter.getPerspective().getWorkingModuleTypeInfo();

      return TypeExpr.canPatternMatchPieces(
          typesFromCachedArgs, targetPieces, contextModuleTypeInfo);
    }
    /**
     * Updates the text and tooltip of the placeholder label.
     *
     * @param polymorphicVarContext used for toString'ing the argument TypeExpr
     */
    public void refreshDisplay(PolymorphicVarContext polymorphicVarContext) {

      TypeExpr leastConstrainedType = valueEntryPanel.getContext().getLeastConstrainedTypeExpr();
      TypeExpr actualType = valueEntryPanel.getValueNode().getTypeExpr();

      // Show the more specialized of the two types..
      if (actualType instanceof TypeVar) {
        placeHolderLabel.setText(
            leastConstrainedType.toString(polymorphicVarContext, namingPolicy));
      } else {
        placeHolderLabel.setText(actualType.toString(polymorphicVarContext, namingPolicy));
      }

      if (getBorder() instanceof LineBorder) {
        setBorder(BorderFactory.createLineBorder(valueEntryPanel.getBackground(), 1));
      }
    }
  /**
   * Get a map from owner value node to type for the child editors used in this editor.
   *
   * @return Map For the owner value node of each child editor used by this editor.
   */
  private Map<ValueNode, TypeExpr> getValueNodeToUnconstrainedTypeMap() {

    Map<ValueNode, TypeExpr> returnMap = new HashMap<ValueNode, TypeExpr>();

    TypeExpr leastConstrainedType = getContext().getLeastConstrainedTypeExpr();
    if (leastConstrainedType.rootTypeVar() != null) {
      leastConstrainedType = getDataConstructor().getTypeExpr().getResultType();
    }

    returnMap.put(getValueNode(), leastConstrainedType);

    for (final DataConstructorEditorPanel childEditor : editorPanelList) {

      returnMap.putAll(childEditor.getValueNodeToUnconstrainedTypeMap(leastConstrainedType));
    }

    return returnMap;
  }
  /**
   * Builds a map from value node to type expression for the value node this panel is editing and
   * the owner value nodes of each value entry panel.
   *
   * @param leastConstrainedType the least constrained type that is allowed
   * @return the resulting map
   */
  Map<ValueNode, TypeExpr> getValueNodeToUnconstrainedTypeMap(TypeExpr leastConstrainedType) {

    Map<ValueNode, TypeExpr> returnMap = new HashMap<ValueNode, TypeExpr>();

    DataConstructor dataCons = getDataConstructor();

    returnMap.put(valueNode, leastConstrainedType);

    for (int i = 0; i < editorPanels.length; i++) {

      ValueNode argValueNode = editorPanels[i].getValueEntryPanel().getOwnerValueNode();

      TypeExpr argType = TypeExpr.getComponentTypeExpr(leastConstrainedType, i, dataCons);

      returnMap.put(argValueNode, argType);
    }

    return returnMap;
  }
  /**
   * Filters the list returned by getAvailableInputTypes to only return the types that can actually
   * pattern match with the value node type expression.
   *
   * @return list of input types to be displayed in the switch type list
   */
  protected Set<TypeExpr> getMatchingInputTypes() {

    Set<TypeExpr> dataTypes = getAvailableInputTypes();
    Set<TypeExpr> matchingTypes = new HashSet<TypeExpr>();
    ModuleTypeInfo currentModuleTypeInfo =
        valueEditorManager.getPerspective().getWorkingModuleTypeInfo();

    if (currentModuleTypeInfo != null) {

      TypeExpr valueNodeTypeExpr = getValueNode().getTypeExpr();

      for (final TypeExpr typeExpr : dataTypes) {

        if (TypeExpr.canPatternMatch(typeExpr, valueNodeTypeExpr, currentModuleTypeInfo)) {
          matchingTypes.add(typeExpr);
        }
      }
    }

    return matchingTypes;
  }
    /**
     * Display value entry controls for all unbound arguments. Precondition: the target must be set.
     */
    private void displayArgumentControls() {

      TableTopPanel tableTopPanel = getTableTopPanel();

      // Build the list of value entry panels and place them onto the TableTop

      // Get the list of unbound arguments
      List<Gem.PartInput> argParts = targetDisplayedGem.getTargetArguments();

      // see if it's ok to use the cached argument values
      boolean canUseCachedArgs = canUseCachedArguments();

      // Figure out the types of the argument panels.  Note that we must account for cached values
      int numArgs = argParts.size();

      // make copies of the input types.
      TypeExpr[] inputTypes = new TypeExpr[numArgs];
      for (int i = 0; i < numArgs; i++) {
        // Get this input, and its type
        Gem.PartInput inputPart = argParts.get(i);
        inputTypes[i] = inputPart.getType();
      }

      TypeExpr[] specializedInputTypes;

      if (canUseCachedArgs) {

        // this is the array of types cached for each argument. If there is no cached type, we just
        // use the argument type.
        TypeExpr[] cachedTypes = new TypeExpr[numArgs];
        for (int i = 0; i < numArgs; ++i) {
          // What we do next depends on whether we use cached sink values
          Gem.PartInput inputPart = argParts.get(i);
          ValueNode cachedVN = gemCutter.getTableTop().getCachedValue(inputPart);
          if (cachedVN != null) {
            cachedTypes[i] = cachedVN.getTypeExpr();
          }
        }

        try {
          ModuleTypeInfo contextModuleTypeInfo =
              gemCutter.getPerspective().getWorkingModuleTypeInfo();
          specializedInputTypes =
              TypeExpr.patternMatchPieces(cachedTypes, inputTypes, contextModuleTypeInfo);
        } catch (TypeException te) {
          throw new IllegalStateException("Error reusing cached args.");
        }
      } else {
        specializedInputTypes = TypeExpr.copyTypeExprs(inputTypes);
      }

      ValueEditorManager valueEditorManager = gemCutter.getValueEditorManager();
      ValueEditorDirector valueEditorDirector = valueEditorManager.getValueEditorDirector();

      Map<PartInput, ValueEditor> inputToEditorMap = new LinkedHashMap<PartInput, ValueEditor>();

      // Step through args, create a new value input panel for each, with the correct value type
      for (int i = 0; i < numArgs; i++) {

        // Get the gem and input
        final Gem.PartInput inputPart = argParts.get(i);
        final Gem inputGem = inputPart.getGem();

        int argumentNumber = inputPart.getInputNum();
        QualifiedName scName = null;

        if (inputGem instanceof FunctionalAgentGem) {
          scName = ((FunctionalAgentGem) inputGem).getName();
        }

        // What we do next depends on whether we use cached sink values
        ValueNode cachedVN;
        final ValueEditor editor;
        if (canUseCachedArgs
            && (cachedVN = gemCutter.getTableTop().getCachedValue(inputPart)) != null) {
          // use cached sink value to generate the VEP

          // get a copy of the cached VN but with the new type expr
          ValueNode newVN = cachedVN.copyValueNode();

          // instantiate the VEP with the new VN (which has the old cached value)
          editor =
              valueEditorDirector.getRootValueEditor(
                  valueEditorHierarchyManager,
                  newVN,
                  scName,
                  argumentNumber,
                  new GemCutterMetadataRunner(gemCutter, inputGem));

        } else {
          // don't use cached sink value.  Generate the default VEP for this sink.
          TypeExpr inputType = specializedInputTypes[i];
          editor =
              valueEditorDirector.getRootValueEditor(
                  valueEditorHierarchyManager,
                  inputType,
                  scName,
                  argumentNumber,
                  new GemCutterMetadataRunner(gemCutter, inputGem));
        }

        // If this is a value entry panel then set the name of the input for use in tooltips
        if (editor instanceof ValueEntryPanel) {
          ((ValueEntryPanel) editor)
              .setArgumentName(inputPart.getArgumentName().getCompositeName());
        }

        // Position the editor next to the connection point.
        positionEditor(editor, inputPart);

        // Now add the editor to the table top.
        tableTopPanel.add(editor, 0);

        // Add to the list of active entry panels
        valueEditorHierarchyManager.addTopValueEditor(editor);

        // Update the context and editor map
        editor.setContext(
            new ValueEditorContext() {
              public TypeExpr getLeastConstrainedTypeExpr() {
                return inputPart.getType();
              }
            });
        inputToEditorMap.put(inputPart, editor);

        // If the editor resizes because it's value changes, make sure it stays aligned to the
        // connection
        editor.addComponentListener(
            new ComponentAdapter() {
              @Override
              public void componentResized(ComponentEvent e) {
                positionEditor((ValueEditor) e.getComponent(), inputPart);
              }
            });
      }

      // Must prevent ValueGems from being editable.
      tableTopPanel.setValueGemsEnabled(false);

      // Need to activate the value editor hierarchy to ensure correct highlighting.
      valueEditorHierarchyManager.activateCurrentEditor();

      // Set the context and type switch listener for each new editor.
      ArgumentValueCommitter argumentValueCommitter =
          new ArgumentValueCommitter(valueEditorManager, inputToEditorMap);

      for (final ValueEditor argumentEditor : getArgumentPanels()) {
        argumentEditor.addValueEditorListener(argumentValueCommitter);
      }

      // May need to show argument controls.
      if (numArgs > 0) {
        gemCutter.showArgumentControls(inputToEditorMap);
        gemCutter.getResetAction().setEnabled(true);
      } else {
        gemCutter.getResetAction().setEnabled(false);
      }
    }
示例#9
0
  /**
   * Note that the types of combinations considered is subject to MAX_BURN_DEPTH.
   *
   * <p>TODOEL: it seems like it would be better if all of the input types in source type pieces
   * were burnable, and it were up to the caller to figure out how the resulting indices mapped onto
   * its arguments.
   *
   * @param destType for example, if connecting to the first argument of the map gem, this would be
   *     a -> b
   * @param sourceTypePieces a list of the relevant source type pieces (ie inputs and output). This
   *     includes the burnable and already burnt inputs in proper order, as well as the output piece
   *     (which should appear at the end). It does NOT include any inputs that are already bound.
   *     For example, in the following case:
   *     <p>Int --\ | \ bound ---------- \ | |- a Boolean (burned) >- / | / Double --/
   *     <p>This would be [Int, Boolean, Double, a]
   * @param unburnedArguments This is an array of indices into sourceTypePieces that should be
   *     considered for burning sites. This can be null, in which case *every* argument is
   *     interpreted as a possible burn site i.e. equivalent to a value of [0, 1, 2, ...,
   *     sourceTypePieces.length - 1]. For example, if the take gem were the source, and it had no
   *     arguments burnt and none bound, this would be [0, 1]. If its 0th argument were burnt this
   *     would be [1]. If both arguments were burnt, this would be []. Note that since these are
   *     indices into sourceTypePieces, if one or more of the arguments are bound, they are not
   *     counted. So, for example, if the first argument of take was bound to another gem and the
   *     second one was unburned, this would be [0]. In the situation illustrated above, this would
   *     be [0, 2].
   * @param info the typeCheck info to use
   * @param sourceGem the source gem, if one exists. Should be null if otherwise.
   */
  private static AutoburnInfo getAutoburnInfoWorker(
      TypeExpr destType,
      TypeExpr[] sourceTypePieces,
      int[] unburnedArguments,
      TypeCheckInfo info,
      Gem sourceGem) {

    // Possible improvements:
    //   Check matchability of the nth argument burn to the nth dest type piece.
    //   Use the fact that some clients do not want to know all burn combos in the ambiguous case.
    //   Use type piece identity (eg. source type pieces which are the same type var..).  Hash for
    // gem types already tried.
    //   Group types when calculating (eg. if try burning one Double, trying to burn the other
    // double will give the same result).
    //   Specialize as burns are applied (eg. makeTransform is Typeable a => a -> a.  If one a is
    // specialized, so is the other).

    // initialize our return type
    AutoburnUnifyStatus unificationStatus = AutoburnUnifyStatus.NOT_POSSIBLE;

    int numUnburned;
    if (unburnedArguments == null) {
      // all positions are burnable
      numUnburned = sourceTypePieces.length - 1;
      unburnedArguments = new int[numUnburned];
      for (int i = 0; i < numUnburned; i++) {
        unburnedArguments[i] = i;
      }
    } else {
      // only some positions are burnable. This corresponds to a gem graph where some arguments have
      // been already burnt
      // by the user, or where some of the arguments do not correspond to arguments in the root gem.
      numUnburned = unburnedArguments.length;
    }

    TypeExpr outputType = sourceTypePieces[sourceTypePieces.length - 1];

    boolean doNotTryBurning = false;
    if (!destType.isFunctionType()) {
      // Don't try burning if the destination type isn't a function.

      // This is a not a logically required restriction. For example, any burnt gem can be connected
      // to the Prelude.id gem.
      // A less trivial example is that Prelude.sin with argument burnt can be connected to the
      // Prelude.typeOf gem. This is because
      // Double -> Double can unify with Typeable a => a.
      // However, it is not a situation that the end user would "likely" want to see in the list and
      // we found that it results in
      // many rare situations.

      doNotTryBurning = true;
    } else {

      // If the destination result type is a type constructor, perform a basic check against the
      // source result type.

      // The fact used here:
      // a. for 2 types to unify, their result types must unify.
      // b. burning doesn't change the result type (in the technical sense of CAL, and not in the
      // sense of the output type displayed
      //   as the result of a gem in the GemCutter. For example, burning the first argument of take
      // gem changes the gem to
      //   in the GemCutter to display as a 1 argument gem with output type Int -> [a], but the
      // overall type of the burnt take gem is
      //   is [a] -> (Int -> [a]) which is just [a] -> Int -> [a] (-> is right associative) and has
      // result type [a], as with the unburnt
      //   take gem.

      TypeExpr destResultTypeExpr = destType.getResultType();
      if (destResultTypeExpr.rootTypeVar() == null
          && !TypeExpr.canUnifyType(
              destResultTypeExpr, outputType.getResultType(), info.getModuleTypeInfo())) {
        // only do the check if the destination result type is a type constructor or record type.
        // the reason for this is that if it is a parameteric type, we are likely to succeed here...
        doNotTryBurning = true;
      }
    }

    int maxTypeCloseness = -1;

    // the type closeness with no burning
    int noBurnTypeCloseness = Integer.MIN_VALUE;

    // The lower bound on the number of burns in the upper range.
    int upperRangeMinBurns = Math.max(numUnburned - MAX_BURN_DEPTH, MAX_BURN_DEPTH + 1);

    // List of all burn combinations for which unification can take place.
    List<BurnCombination> burnCombos = new ArrayList<BurnCombination>();

    // Our combination generator will create all combinations of a fixed size.
    // So, we iterate over all possible sizes.
    // However, we skip over sizes between MAX_BURN_DEPTH and upperRangeMinBurns
    // so that our algorithm runs in polynomial ( specifically O(n^MAX_BURN_DEPTH) ) time
    // rather than exponential ( O(2^n) ).
    for (int numBurns = 0; numBurns <= numUnburned; numBurns++) {
      if (doNotTryBurning && numBurns > 0) {
        break;
      }

      // If necessary, skip from MAX_BURN_DEPTH to upperRangeMinBurns.
      if (numBurns == MAX_BURN_DEPTH + 1) {
        numBurns = upperRangeMinBurns;
      }

      // Iterate through all the combinations of burning inputs of size numBurns.
      boolean isValidCombo = true;
      for (int[] burnComboArray = getFirstCombination(numBurns);
          isValidCombo;
          isValidCombo = getNextCombination(burnComboArray, numUnburned)) {

        // calculate what the corresponding output type would be.
        // Note that we are assuming that the elements of burnComboArray are in
        // ascending order.
        int currentSourceTypePiece = sourceTypePieces.length - 2;
        int nextUnburnedArg = numUnburned - 1;
        int nextArgToBurn = numBurns - 1;

        // Loop over currentSourceTypePieces.
        // These represent all the burned and unburned arguments.

        TypeExpr newOutputType = outputType;
        while (currentSourceTypePiece >= 0) {
          boolean shouldBurn = false;
          if (nextUnburnedArg >= 0
              && currentSourceTypePiece == unburnedArguments[nextUnburnedArg]) {
            // This is an unburned argument.
            if (nextArgToBurn >= 0 && nextUnburnedArg == burnComboArray[nextArgToBurn]) {
              // This is a burnable argument that we want to try burning
              shouldBurn = true;
              nextArgToBurn--;
            }
            nextUnburnedArg--;
          } else {
            // This is a burned argument.
            shouldBurn = true;
          }
          if (shouldBurn) {
            // We need to add the corresponding type to the output type
            TypeExpr argType = sourceTypePieces[currentSourceTypePiece];
            newOutputType = TypeExpr.makeFunType(argType, newOutputType);
          }
          currentSourceTypePiece--;
        }

        // Would the resulting output type unify with the type we want?
        int typeCloseness =
            TypeExpr.getTypeCloseness(destType, newOutputType, info.getModuleTypeInfo());
        if (numBurns == 0) {
          assert (noBurnTypeCloseness == Integer.MIN_VALUE);
          noBurnTypeCloseness = typeCloseness;
        }

        if (typeCloseness >= 0) {
          if (typeCloseness > maxTypeCloseness) {
            maxTypeCloseness = typeCloseness;
          }

          boolean autoburnable = false;
          if (numBurns == 0) {

            // We didn't have to autoburn.
            unificationStatus = AutoburnUnifyStatus.NOT_NECESSARY;

          } else if (unificationStatus == AutoburnUnifyStatus.NOT_POSSIBLE) {

            // We have our first valid combo and it is not possible to unify without burning.
            autoburnable = true;
            unificationStatus = AutoburnUnifyStatus.UNAMBIGUOUS;

          } else if (unificationStatus == AutoburnUnifyStatus.NOT_NECESSARY) {

            // We have our first valid combo and it is possible to unify without burning.
            autoburnable = true;
            unificationStatus = AutoburnUnifyStatus.UNAMBIGUOUS_NOT_NECESSARY;

          } else {
            // We got > 1 valid combos.
            autoburnable = true;

            if (unificationStatus == AutoburnUnifyStatus.UNAMBIGUOUS) {
              unificationStatus = AutoburnUnifyStatus.AMBIGUOUS;

            } else if (unificationStatus == AutoburnUnifyStatus.UNAMBIGUOUS_NOT_NECESSARY) {
              unificationStatus = AutoburnUnifyStatus.AMBIGUOUS_NOT_NECESSARY;
            }
          }

          // If unify can take place, copy the array and store it in burn combos.
          if (autoburnable) {
            int[] autoburnableCombo;
            if (sourceGem != null) {
              autoburnableCombo = translateBurnCombo(burnComboArray, sourceGem);
            } else {
              autoburnableCombo = new int[numBurns];
              System.arraycopy(burnComboArray, 0, autoburnableCombo, 0, numBurns);
            }

            burnCombos.add(new BurnCombination(autoburnableCombo, typeCloseness));
          }
        }
      }
    }

    if (unificationStatus == AutoburnUnifyStatus.NOT_POSSIBLE) {
      burnCombos = null;
    }

    return new AutoburnInfo(unificationStatus, burnCombos, maxTypeCloseness, noBurnTypeCloseness);
  }
示例#10
0
  /**
   * Return whether autoburning will result in a unification. Only inputs on the given gem will be
   * considered for burning.
   *
   * @param destType The destination type to unify with.
   * @param gem The gem on which to attempt "autoburning".
   * @param info the typeCheck info to use.
   * @return AutoburnLogic.AutoburnInfo autoburnable result.
   */
  public static AutoburnInfo getAutoburnInfo(TypeExpr destType, Gem gem, TypeCheckInfo info) {
    // see if we got a real type
    if (destType == null) {
      return AutoburnInfo.makeNoUnificationPossibleAutoburnInfo();
    }

    int[] burnableArgIndices;
    List<TypeExpr> sourceTypeList = new ArrayList<TypeExpr>(); // input types, then output type.

    TypeExpr outType =
        (gem instanceof CollectorGem)
            ? ((CollectorGem) gem).getCollectingPart().getType()
            : gem.getOutputPart().getType();
    TypeExpr[] outTypePieces = outType.getTypePieces();

    // A collector gem's collecting part is not burnable.
    if (gem instanceof CollectorGem && !((CollectorGem) gem).isConnected()) {
      burnableArgIndices = new int[0];

    } else {
      // declare the burnt arg indices array
      burnableArgIndices = new int[gem.getNInputs()];

      if (burnableArgIndices.length != 0) {
        // get the unbound input parts of the gem and keep track of which ones are burnable
        int unburnedCount = 0;
        int burnedCount = 0;
        for (int i = 0, n = gem.getNInputs(); i < n; i++) {
          Gem.PartInput input = gem.getInputPart(i);

          if (!input.isConnected()) {
            if (input.isBurnt()) {
              sourceTypeList.add(outTypePieces[burnedCount]);
              burnedCount++;
            } else {
              sourceTypeList.add(input.getType());
              burnableArgIndices[unburnedCount] = sourceTypeList.size() - 1;
              unburnedCount++;
            }
          }
        }

        // Calculate what the output type would be if none of the arguments were burned.
        TypeExpr newOutType = outTypePieces[outTypePieces.length - 1];
        for (int i = outTypePieces.length - 2; i >= burnedCount; i--) {
          newOutType = TypeExpr.makeFunType(outTypePieces[i], newOutType);
        }
        outType = newOutType;

        // Assign to a new trimmed down array
        int[] newIndices = new int[unburnedCount];
        System.arraycopy(burnableArgIndices, 0, newIndices, 0, unburnedCount);
        burnableArgIndices = newIndices;
      }
    }

    // Add the output type to the source types.
    sourceTypeList.add(outType);

    return getAutoburnInfoWorker(
        destType, sourceTypeList.toArray(new TypeExpr[0]), burnableArgIndices, info, gem);
  }