@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); } }
protected boolean hasSelfArgument() { Class<?> clazz = getObjectType(); if (clazz == null) { return false; } List<SkylarkType> types = signature.getTypes(); ImmutableList<String> names = signature.getSignature().getNames(); return (!types.isEmpty() && types.get(0).canBeCastTo(clazz)) || (!names.isEmpty() && names.get(0).equals("self")); }
/** check types and convert as required */ protected void canonicalizeArguments(Object[] arguments, Location loc) throws EvalException { // TODO(bazel-team): maybe link syntax.SkylarkType and package.Type, // so we can simultaneously typecheck and convert? // Note that a BuiltinFunction already does typechecking of simple types. List<SkylarkType> types = getEnforcedArgumentTypes(); // Check types, if supplied if (types == null) { return; } List<String> names = signature.getSignature().getNames(); int length = types.size(); for (int i = 0; i < length; i++) { Object value = arguments[i]; SkylarkType type = types.get(i); if (value != null && type != null && !type.contains(value)) { throw new EvalException( loc, String.format( "expected %s for '%s' while calling %s but got %s instead: %s", type, names.get(i), getName(), EvalUtils.getDataTypeName(value, true), value)); } } }
/** * Returns the signature as "[className.]methodName(name1: paramType1, name2: paramType2, ...)" or * "[className.]methodName(paramType1, paramType2, ...)", depending on the value of showNames. */ public String getShortSignature(boolean showNames) { StringBuilder builder = new StringBuilder(); boolean hasSelf = hasSelfArgument(); builder.append(getFullName()).append("("); signature.toStringBuilder(builder, showNames, false, false, hasSelf); builder.append(")"); return builder.toString(); }
/** Render this object in the form of an equivalent Python function signature. */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getName()); if (signature != null) { sb.append('('); signature.toStringBuilder(sb); sb.append(')'); } // if unconfigured, don't even output parentheses return sb.toString(); }
@Override void exec(Environment env) throws EvalException, InterruptedException { List<Expression> defaultExpressions = signature.getDefaultValues(); ArrayList<Object> defaultValues = null; ArrayList<SkylarkType> types = null; if (defaultExpressions != null) { defaultValues = new ArrayList<>(defaultExpressions.size()); for (Expression expr : defaultExpressions) { defaultValues.add(expr.eval(env)); } } env.update( ident.getName(), new UserDefinedFunction( ident, FunctionSignature.WithValues.<Object, SkylarkType>create( signature.getSignature(), defaultValues, types), statements, (SkylarkEnvironment) env)); }
protected MixedModeFunction( String name, Iterable<String> parameters, int numMandatoryParameters, boolean onlyNamedArguments, Location location) { super(name); this.parameters = ImmutableList.copyOf(parameters); this.numMandatoryParameters = numMandatoryParameters; this.onlyNamedArguments = onlyNamedArguments; this.location = location; // Fake a signature from the above this.signature = FunctionSignature.WithValues.<Object, SkylarkType>create( FunctionSignature.of(numMandatoryParameters, this.parameters.toArray(new String[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; }
@Override public Object call( List<Object> args, Map<String, Object> kwargs, FuncallExpression ast, Environment env) throws EvalException, InterruptedException { // ast is null when called from Java (as there's no Skylark call site). Location loc = ast == null ? location : ast.getLocation(); if (onlyNamedArguments && !args.isEmpty()) { throw new EvalException(loc, getSignature() + " does not accept positional arguments"); } if (kwargs == null) { kwargs = ImmutableMap.<String, Object>of(); } int numParams = parameters.size(); int numArgs = args.size(); Object[] namedArguments = new Object[numParams]; // first, positional arguments: if (numArgs > numParams) { throw new EvalException(loc, "too many positional arguments in call to " + getSignature()); } for (int ii = 0; ii < numArgs; ++ii) { namedArguments[ii] = args.get(ii); } // TODO(bazel-team): here, support *varargs splicing // second, keyword arguments: for (Map.Entry<String, Object> entry : kwargs.entrySet()) { String keyword = entry.getKey(); int pos = parameters.indexOf(keyword); if (pos == -1) { List<String> unexpected = listDifference(new ArrayList<>(kwargs.keySet()), parameters); Collections.sort(unexpected); // issue stable error messages. throw new EvalException( loc, "unexpected keyword" + (unexpected.size() > 1 ? "s" : "") + " '" + Joiner.on("', '").join(unexpected) + "' in call to " + getSignature()); } else { if (namedArguments[pos] != null) { throw new EvalException( loc, getSignature() + " got multiple values for keyword argument '" + keyword + "'"); } namedArguments[pos] = kwargs.get(keyword); } } // third, check mandatory parameters: for (int ii = 0; ii < numMandatoryParameters; ++ii) { if (namedArguments[ii] == null) { throw new EvalException(loc, getSignature() + " received insufficient arguments"); } } // fourth, fill in defaults from the signature, if any List<Object> defaults = signature.getDefaultValues(); if (defaults != null) { int jj = 0; for (int ii = numMandatoryParameters; ii < numParams; ++ii) { if (namedArguments[ii] == null) { namedArguments[ii] = defaults.get(jj); } jj++; } } try { return call(namedArguments, ast, env); } catch (ConversionException | IllegalArgumentException | IllegalStateException | ClassCastException e) { throw new EvalException(loc, e.getMessage()); } }
/** Create a function using a signature without defaults */ public MixedModeFunction(String name, FunctionSignature signature) { this(name, FunctionSignature.WithValues.<Object, SkylarkType>create(signature), null); }
/** Configure a function based on its signature */ protected void configure() { // this function is called after the signature was initialized Preconditions.checkState(signature != null); enforcedArgumentTypes = signature.getTypes(); }
/** * 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; }
/** The size of the array required by the callee. */ protected int getArgArraySize() { return signature.getSignature().getShape().getArguments(); }