示例#1
0
  @Override
  void validate(final ValidationEnvironment env) throws EvalException {
    ValidationEnvironment localEnv = new ValidationEnvironment(env);
    FunctionSignature sig = signature.getSignature();
    FunctionSignature.Shape shape = sig.getShape();
    ImmutableList<String> names = sig.getNames();
    List<Expression> defaultExpressions = signature.getDefaultValues();

    int positionals = shape.getPositionals();
    int mandatoryPositionals = shape.getMandatoryPositionals();
    int namedOnly = shape.getNamedOnly();
    int mandatoryNamedOnly = shape.getMandatoryNamedOnly();
    boolean starArg = shape.hasStarArg();
    boolean kwArg = shape.hasKwArg();
    int named = positionals + namedOnly;
    int args = named + (starArg ? 1 : 0) + (kwArg ? 1 : 0);
    int startOptionals = mandatoryPositionals;
    int endOptionals = named - mandatoryNamedOnly;

    int j = 0; // index for the defaultExpressions
    for (int i = 0; i < args; i++) {
      String name = names.get(i);
      if (startOptionals <= i && i < endOptionals) {
        defaultExpressions.get(j++).validate(env);
      }
      localEnv.declare(name, getLocation());
    }
    for (Statement stmts : statements) {
      stmts.validate(localEnv);
    }
  }
示例#2
0
  /** Create a function using a signature with defaults */
  public MixedModeFunction(
      String name, FunctionSignature.WithValues<Object, SkylarkType> signature, Location location) {
    super(name);

    // TODO(bazel-team): lift the following limitations, by actually implementing
    // the full function call protocol.
    FunctionSignature sig = signature.getSignature();
    FunctionSignature.Shape shape = sig.getShape();
    Preconditions.checkArgument(
        !shape.hasKwArg() && !shape.hasStarArg() && shape.getNamedOnly() == 0,
        "no star, star-star or named-only parameters (for now)");

    this.signature = signature;
    this.parameters = ImmutableList.copyOf(sig.getNames());
    this.numMandatoryParameters = shape.getMandatoryPositionals();
    this.onlyNamedArguments = false;
    this.location = location;
  }
示例#3
0
  /**
   * Generates a subclass of {@link CompiledFunction} with a static method "call" and static methods
   * for getting information from a {@link DebugInfo} instance.
   *
   * <p>The "call" method contains the compiled version of this function's AST.
   */
  private Optional<Method> buildCompiledFunction() throws EvalException {
    // replace the / character in the path so we have file system compatible class names
    // the java specification mentions that $ should be used in generated code
    // see http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.8
    String path =
        location.getPath() != null ? location.getPath().getPathString().replace('/', '$') : "";
    String compiledFunctionClassName =
        CompiledFunction.class.getCanonicalName() + path + "$" + getName();
    compilerDebug("Compiling " + getLocationPathAndLine() + " " + getName());
    try {
      int publicStatic = Visibility.PUBLIC.getMask() | Ownership.STATIC.getMask();
      TypeDescription.Latent latentCompiledFunctionClass =
          new TypeDescription.Latent(
              compiledFunctionClassName,
              publicStatic | TypeManifestation.FINAL.getMask(),
              new TypeDescription.ForLoadedType(CompiledFunction.class),
              Collections.<TypeDescription>emptyList());
      MethodDescription getAstNode =
          new MethodDescription.Latent(
              latentCompiledFunctionClass,
              new MethodDescription.Token(
                  "getAstNode",
                  publicStatic | MethodManifestation.FINAL.getMask(),
                  new TypeDescription.ForLoadedType(ASTNode.class),
                  Arrays.asList(new TypeDescription.ForLoadedType(int.class))));
      MethodDescription getLocation =
          new MethodDescription.Latent(
              latentCompiledFunctionClass,
              new MethodDescription.Token(
                  "getLocation",
                  publicStatic | MethodManifestation.FINAL.getMask(),
                  new TypeDescription.ForLoadedType(Location.class),
                  Arrays.asList(new TypeDescription.ForLoadedType(int.class))));

      DebugInfo debugInfo = new DebugInfo(getAstNode, getLocation);
      FunctionSignature sig = signature.getSignature();
      VariableScope scope = VariableScope.function(sig.getNames());
      Implementation compiledImplementation = compileBody(scope, debugInfo);

      List<Class<?>> parameterTypes = sig.getShape().toClasses();
      parameterTypes.add(Environment.class);
      Unloaded<CompiledFunction> unloadedImplementation =
          new ByteBuddy()
              .withClassVisitor(new StackMapFrameClassVisitor(debugCompilerPrintByteCode))
              .subclass(CompiledFunction.class)
              .name(compiledFunctionClassName)
              .defineMethod(
                  "call",
                  Object.class,
                  parameterTypes,
                  Visibility.PUBLIC,
                  Ownership.STATIC,
                  MethodManifestation.FINAL)
              .intercept(compiledImplementation)
              .defineMethod(getAstNode)
              // TODO(bazel-team) unify the two delegate fields into one, probably needs a custom
              // ImplementationDelegate that adds it only once? or just create the static field
              // itself with the correct value and create getAstNode & getLocation with a custom
              // implementation using it
              .intercept(
                  MethodDelegation.to(debugInfo, DebugInfo.class, "getAstNodeDelegate")
                      .filter(ElementMatchers.named("getAstNode")))
              .defineMethod(getLocation)
              .intercept(
                  MethodDelegation.to(debugInfo, DebugInfo.class, "getLocationDelegate")
                      .filter(ElementMatchers.named("getLocation")))
              .make();
      saveByteCode(unloadedImplementation);
      Class<? extends CompiledFunction> functionClass =
          unloadedImplementation
              .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
              .getLoaded();

      return Optional.of(
          ReflectionUtils.getMethod(
                  functionClass,
                  "call",
                  parameterTypes.toArray(new Class<?>[parameterTypes.size()]))
              .getLoadedMethod());
    } catch (EvalException e) {
      // don't capture EvalExceptions
      throw e;
    } catch (Throwable e) {
      compilerDebug("Error while compiling", e);
      // TODO(bazel-team) don't capture all throwables? couldn't compile this, log somewhere?
    }
    return Optional.absent();
  }
示例#4
0
  /**
   * Process the caller-provided arguments into an array suitable for the callee (this function).
   */
  public Object[] processArguments(
      List<Object> args, @Nullable Map<String, Object> kwargs, @Nullable Location loc)
      throws EvalException {

    Object[] arguments = new Object[getArgArraySize()];

    // extract function signature
    FunctionSignature sig = signature.getSignature();
    FunctionSignature.Shape shape = sig.getShape();
    ImmutableList<String> names = sig.getNames();
    List<Object> defaultValues = signature.getDefaultValues();

    // Note that this variable will be adjusted down if there are extra positionals,
    // after these extra positionals are dumped into starParam.
    int numPositionalArgs = args.size();

    int numMandatoryPositionalParams = shape.getMandatoryPositionals();
    int numOptionalPositionalParams = shape.getOptionalPositionals();
    int numMandatoryNamedOnlyParams = shape.getMandatoryNamedOnly();
    int numOptionalNamedOnlyParams = shape.getOptionalNamedOnly();
    boolean hasStarParam = shape.hasStarArg();
    boolean hasKwParam = shape.hasKwArg();
    int numPositionalParams = numMandatoryPositionalParams + numOptionalPositionalParams;
    int numNamedOnlyParams = numMandatoryNamedOnlyParams + numOptionalNamedOnlyParams;
    int numNamedParams = numPositionalParams + numNamedOnlyParams;
    int kwParamIndex = names.size() - 1; // only valid if hasKwParam

    // (1) handle positional arguments
    if (hasStarParam) {
      // Nota Bene: we collect extra positional arguments in a (tuple,) rather than a [list],
      // and this is actually the same as in Python.
      int starParamIndex = numNamedParams;
      if (numPositionalArgs > numPositionalParams) {
        arguments[starParamIndex] =
            Tuple.copyOf(args.subList(numPositionalParams, numPositionalArgs));
        numPositionalArgs = numPositionalParams; // clip numPositionalArgs
      } else {
        arguments[starParamIndex] = Tuple.EMPTY;
      }
    } else if (numPositionalArgs > numPositionalParams) {
      throw new EvalException(
          loc,
          numPositionalParams > 0
              ? "too many (" + numPositionalArgs + ") positional arguments in call to " + this
              : this + " does not accept positional arguments, but got " + numPositionalArgs);
    }

    for (int i = 0; i < numPositionalArgs; i++) {
      arguments[i] = args.get(i);
    }

    // (2) handle keyword arguments
    if (kwargs == null || kwargs.isEmpty()) {
      // Easy case (2a): there are no keyword arguments.
      // All arguments were positional, so check we had enough to fill all mandatory positionals.
      if (numPositionalArgs < numMandatoryPositionalParams) {
        throw new EvalException(
            loc,
            String.format(
                "insufficient arguments received by %s (got %s, expected at least %s)",
                this, numPositionalArgs, numMandatoryPositionalParams));
      }
      // We had no named argument, so fail if there were mandatory named-only parameters
      if (numMandatoryNamedOnlyParams > 0) {
        throw new EvalException(
            loc, String.format("missing mandatory keyword arguments in call to %s", this));
      }
      // Fill in defaults for missing optional parameters, that were conveniently grouped together,
      // thanks to the absence of mandatory named-only parameters as checked above.
      if (defaultValues != null) {
        int j = numPositionalArgs - numMandatoryPositionalParams;
        int endOptionalParams = numPositionalParams + numOptionalNamedOnlyParams;
        for (int i = numPositionalArgs; i < endOptionalParams; i++) {
          arguments[i] = defaultValues.get(j++);
        }
      }
      // If there's a kwParam, it's empty.
      if (hasKwParam) {
        // TODO(bazel-team): create a fresh mutable dict, like Python does
        arguments[kwParamIndex] = ImmutableMap.<String, Object>of();
      }
    } else if (hasKwParam && numNamedParams == 0) {
      // Easy case (2b): there are no named parameters, but there is a **kwParam.
      // Therefore all keyword arguments go directly to the kwParam.
      // Note that *starParam and **kwParam themselves don't count as named.
      // Also note that no named parameters means no mandatory parameters that weren't passed,
      // and no missing optional parameters for which to use a default. Thus, no loops.
      // TODO(bazel-team): create a fresh mutable dict, like Python does
      arguments[kwParamIndex] = kwargs; // NB: not 2a means kwarg isn't null
    } else {
      // Hard general case (2c): some keyword arguments may correspond to named parameters
      HashMap<String, Object> kwArg = hasKwParam ? new HashMap<String, Object>() : null;

      // For nicer stabler error messages, start by checking against
      // an argument being provided both as positional argument and as keyword argument.
      ArrayList<String> bothPosKey = new ArrayList<>();
      for (int i = 0; i < numPositionalArgs; i++) {
        String name = names.get(i);
        if (kwargs.containsKey(name)) {
          bothPosKey.add(name);
        }
      }
      if (!bothPosKey.isEmpty()) {
        throw new EvalException(
            loc,
            String.format(
                "argument%s '%s' passed both by position and by name in call to %s",
                (bothPosKey.size() > 1 ? "s" : ""), Joiner.on("', '").join(bothPosKey), this));
      }

      // Accept the arguments that were passed.
      for (Map.Entry<String, Object> entry : kwargs.entrySet()) {
        String keyword = entry.getKey();
        Object value = entry.getValue();
        int pos = names.indexOf(keyword); // the list should be short, so linear scan is OK.
        if (0 <= pos && pos < numNamedParams) {
          arguments[pos] = value;
        } else {
          if (!hasKwParam) {
            List<String> unexpected =
                Ordering.natural()
                    .sortedCopy(
                        Sets.difference(
                            kwargs.keySet(),
                            ImmutableSet.copyOf(names.subList(0, numNamedParams))));
            throw new EvalException(
                loc,
                String.format(
                    "unexpected keyword%s '%s' in call to %s",
                    unexpected.size() > 1 ? "s" : "", Joiner.on("', '").join(unexpected), this));
          }
          if (kwArg.containsKey(keyword)) {
            throw new EvalException(
                loc,
                String.format("%s got multiple values for keyword argument '%s'", this, keyword));
          }
          kwArg.put(keyword, value);
        }
      }
      if (hasKwParam) {
        // TODO(bazel-team): create a fresh mutable dict, like Python does
        arguments[kwParamIndex] = ImmutableMap.copyOf(kwArg);
      }

      // Check that all mandatory parameters were filled in general case 2c.
      // Note: it's possible that numPositionalArgs > numMandatoryPositionalParams but that's OK.
      for (int i = numPositionalArgs; i < numMandatoryPositionalParams; i++) {
        if (arguments[i] == null) {
          throw new EvalException(
              loc,
              String.format(
                  "missing mandatory positional argument '%s' while calling %s",
                  names.get(i), this));
        }
      }

      int endMandatoryNamedOnlyParams = numPositionalParams + numMandatoryNamedOnlyParams;
      for (int i = numPositionalParams; i < endMandatoryNamedOnlyParams; i++) {
        if (arguments[i] == null) {
          throw new EvalException(
              loc,
              String.format(
                  "missing mandatory named-only argument '%s' while calling %s",
                  names.get(i), this));
        }
      }

      // Get defaults for those parameters that weren't passed.
      if (defaultValues != null) {
        for (int i = Math.max(numPositionalArgs, numMandatoryPositionalParams);
            i < numPositionalParams;
            i++) {
          if (arguments[i] == null) {
            arguments[i] = defaultValues.get(i - numMandatoryPositionalParams);
          }
        }
        int numMandatoryParams = numMandatoryPositionalParams + numMandatoryNamedOnlyParams;
        for (int i = numMandatoryParams + numOptionalPositionalParams; i < numNamedParams; i++) {
          if (arguments[i] == null) {
            arguments[i] = defaultValues.get(i - numMandatoryParams);
          }
        }
      }
    } // End of general case 2c for argument passing.

    return arguments;
  }