@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); } }
/** Configure the reflection mechanism */ @Override protected void configure() { invokeMethod = findMethod("invoke"); int arguments = signature.getSignature().getShape().getArguments(); innerArgumentCount = arguments + (extraArgs == null ? 0 : extraArgs.length); Class<?>[] parameterTypes = invokeMethod.getParameterTypes(); Preconditions.checkArgument( innerArgumentCount == parameterTypes.length, "bad argument count for %s: method has %s arguments, type list has %s", getName(), innerArgumentCount, parameterTypes.length); if (enforcedArgumentTypes != null) { for (int i = 0; i < arguments; i++) { SkylarkType enforcedType = enforcedArgumentTypes.get(i); if (enforcedType != null) { Class<?> parameterType = parameterTypes[i]; String msg = String.format( "fun %s(%s), param %s, enforcedType: %s (%s); parameterType: %s", getName(), signature, signature.getSignature().getNames().get(i), enforcedType, enforcedType.getType(), parameterType); if (enforcedType instanceof SkylarkType.Simple || enforcedType instanceof SkylarkFunctionType) { Preconditions.checkArgument(enforcedType.getType() == parameterType, msg); // No need to enforce Simple types on the Skylark side, the JVM will do it for us. enforcedArgumentTypes.set(i, null); } else if (enforcedType instanceof SkylarkType.Combination) { Preconditions.checkArgument(enforcedType.getType() == parameterType, msg); } else { Preconditions.checkArgument( parameterType == Object.class || parameterType == null, msg); } } } } // No need for the enforcedArgumentTypes List if all the types were Simple enforcedArgumentTypes = FunctionSignature.<SkylarkType>valueListOrNull(enforcedArgumentTypes); if (returnType != null) { Class<?> type = returnType; if (type == HackHackEitherList.class) { type = Object.class; } Class<?> methodReturnType = invokeMethod.getReturnType(); Preconditions.checkArgument( type == methodReturnType, "signature for function %s says it returns %s but its invoke method returns %s", getName(), returnType, methodReturnType); } }
public void convertCallbacks( List<FunctionSignature> functionSignatures, Signatures signatures, DeclarationsHolder out, Identifier libraryClassName) { if (functionSignatures != null) { for (FunctionSignature functionSignature : functionSignatures) { if (functionSignature.findParentOfType(Struct.class) != null) continue; Arg a = functionSignature.findParentOfType(Arg.class); if (a != null && a.getParentElement() == null) continue; // TODO understand why we end up having an orphan Arg here !!!! convertCallback(functionSignature, signatures, out, libraryClassName); } } }
/** 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 ReturnState createFunction(RpcController controller, FunctionDescProto funcDesc) { try { FunctionSignature signature = FunctionSignature.create(funcDesc); if (functions.containsKey(funcDesc.getSignature())) { FunctionDescProto found = findFunctionStrictType(funcDesc, true); if (found != null) { return errDuplicateFunction(signature.toString()); } } TUtil.putToNestedList(functions, funcDesc.getSignature().getName(), funcDesc); return OK; } catch (Throwable t) { printStackTraceIfError(LOG, t); return returnError(t); } }
public Struct convertCallback( FunctionSignature functionSignature, Signatures signatures, Identifier callerLibraryName) { Identifier name = result.typeConverter.inferCallBackName(functionSignature, true, false, callerLibraryName); if (name == null) return null; name = result.typeConverter.getValidJavaArgumentName(name); Function function = functionSignature.getFunction(); int i = 1; Identifier chosenName = name; while (!(signatures.addClass(chosenName))) { chosenName = ident(name.toString() + (++i)); } Element parent = functionSignature.getParentElement(); Element comel = parent != null && parent instanceof TypeDef ? parent : functionSignature; Struct callbackStruct = new Struct(); configureCallbackStruct(callbackStruct); // callbackStruct.setParents(Arrays.asList(getCallbackType(functionSignature, chosenName))); callbackStruct.setTag(ident(chosenName)); if (!result.config.noComments) callbackStruct.addToCommentBefore( comel.getCommentBefore(), comel.getCommentAfter(), getFileCommentContent(comel)); convertFunction(function, new Signatures(), true, callbackStruct, callerLibraryName, -1); for (Declaration d : callbackStruct.getDeclarations()) { if (d instanceof Function) { callbackStruct.addAnnotations(callbackStruct.getAnnotations()); callbackStruct.setAnnotations(null); break; } } return callbackStruct; }
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]))); }
/** * 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(); }
/** * 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; }