@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); } }
/** 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; }
/** * 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; }