public EnumNameCallChecker(JProgram jprogram, TreeLogger logger) { this.logger = logger; this.enumNameMethod = jprogram.getIndexedMethod("Enum.name"); this.enumToStringMethod = jprogram.getIndexedMethod("Enum.toString"); this.classType = jprogram.getIndexedType("Class"); this.enumType = jprogram.getIndexedType("Enum"); this.stringType = jprogram.getIndexedType("String"); /* * Find the correct version of enumValueOfMethod. * * Note: it doesn't work to check against a ref returned by * jprogram.getIndexedMethod("Enum.valueOf"), since there are 2 different * versions of Enum.valueOf in our jre emulation library, and the indexed * ref won't reliably flag the public instance, which is the one we want * here (i.e. Enum.valueOf(Class<T>,String)). The other version is * protected, but is called by the generated constructors for sub-classes * of Enum, and we don't want to warn for those cases. */ JMethod foundMethod = null; List<JMethod> enumMethods = enumType.getMethods(); for (JMethod enumMethod : enumMethods) { if ("valueOf".equals(enumMethod.getName())) { List<JParameter> jParameters = enumMethod.getParams(); if (jParameters.size() == 2 && jParameters.get(0).getType() == classType && jParameters.get(1).getType() == stringType) { foundMethod = enumMethod; break; } } } this.enumValueOfMethod = foundMethod; }
private JLocal newExceptionVariable(SourceInfo sourceInfo) { return JProgram.createLocal( sourceInfo, "$e" + catchVariableIndex++, program.getTypeJavaLangObject(), false, currentMethodBody); }
/** * Transform a call to a pruned instance method (or static impl) into a call to the null method, * which will be used to replace <code>x</code>. */ public static JMethodCall transformToNullMethodCall(JMethodCall x, JProgram program) { JExpression instance = x.getInstance(); List<JExpression> args = x.getArgs(); if (program.isStaticImpl(x.getTarget())) { instance = args.get(0); args = args.subList(1, args.size()); } else { /* * We assert that method must be non-static, otherwise it would have been * rescued. */ // assert !x.getTarget().isStatic(); /* * HACK HACK HACK: ControlFlowAnalyzer has special hacks for dealing with * ClassLiterals, which causes the body of ClassLiteralHolder's clinit to * never be rescured. This in turn causes invalid references to static * methods, which violates otherwise good assumptions about compiler * operation. * * TODO: Remove this when ControlFlowAnalyzer doesn't special-case * CLH.clinit(). */ if (x.getTarget().isStatic() && instance == null) { instance = program.getLiteralNull(); } } assert (instance != null); if (!instance.hasSideEffects()) { instance = program.getLiteralNull(); } JMethodCall newCall = new JMethodCall( x.getSourceInfo(), instance, program.getNullMethod(), primitiveTypeOrNullTypeOrArray(program, x.getType())); // Retain the original arguments, they will be evaluated for side effects. for (JExpression arg : args) { if (arg.hasSideEffects()) { newCall.addArg(arg); } } return newCall; }
/** Returns an ast node representing the expression {@code expression != null}. */ public static JExpression createOptimizedNotNullComparison( JProgram program, SourceInfo info, JExpression expression) { JReferenceType type = (JReferenceType) expression.getType(); if (type.isNullType()) { return program.getLiteralBoolean(false); } if (!type.canBeNull()) { return createOptimizedMultiExpression(expression, program.getLiteralBoolean(true)); } return new JBinaryOperation( info, program.getTypePrimitiveBoolean(), JBinaryOperator.NEQ, expression, program.getLiteralNull()); }
public static JProgram construct( TreeLogger logger, CompilationState state, Properties properties, String... entryPoints) throws UnableToCompleteException { PrecompileTaskOptions options = new PrecompileTaskOptionsImpl(); options.setEnableAssertions(true); JProgram jprogram = AstConstructor.construct(logger, state, options, properties); // Add entry methods for entry points. for (String entryPoint : entryPoints) { JDeclaredType entryType = jprogram.getFromTypeMap(entryPoint); for (JMethod method : entryType.getMethods()) { if (method.isStatic() && JProgram.isClinit(method)) { jprogram.addEntryMethod(method); } } } // Tree is now ready to optimize. return jprogram; }
/** Return the smallest type that is is a subtype of the argument. */ static JType primitiveTypeOrNullTypeOrArray(JProgram program, JType type) { if (type instanceof JArrayType) { JType leafType = primitiveTypeOrNullTypeOrArray(program, ((JArrayType) type).getLeafType()); return program.getOrCreateArrayType(leafType, ((JArrayType) type).getDims()); } if (type instanceof JPrimitiveType) { return type; } return JReferenceType.NULL_TYPE; }
@Override protected boolean optimizeMethod(JProgram program, JMethod method) { program.addEntryMethod(findMainMethod(program)); boolean didChange = false; // TODO(jbrosenberg): remove loop when Pruner/CFA interaction is perfect. while (TypeTightener.exec(program).didChange()) { didChange = true; } return didChange; }
/** * Return the JSNI signature for a member. Leave off the return type for a method signature, so as * to match what a user would type in as a JsniRef. */ private static String getJsniSignature(HasEnclosingType member, boolean wildcardParams) { if (member instanceof JField) { return ((JField) member).getName(); } JMethod method = (JMethod) member; if (wildcardParams) { return method.getName() + "(" + JsniRef.WILDCARD_PARAM_LIST + ")"; } else { return JProgram.getJsniSig(method, false); } }
/** * Transform a reference to a pruned instance field into a reference to the null field, which will * be used to replace <code>x</code>. */ public static JFieldRef transformToNullFieldRef(JFieldRef x, JProgram program) { JExpression instance = x.getInstance(); /* * We assert that field must be non-static if it's an rvalue, otherwise it * would have been rescued. */ // assert !x.getField().isStatic(); /* * HACK HACK HACK: ControlFlowAnalyzer has special hacks for dealing with * ClassLiterals, which causes the body of ClassLiteralHolder's clinit to * never be rescured. This in turn causes invalid references to static * methods, which violates otherwise good assumptions about compiler * operation. * * TODO: Remove this when ControlFlowAnalyzer doesn't special-case * CLH.clinit(). */ if (x.getField().isStatic() && instance == null) { instance = program.getLiteralNull(); } assert instance != null; if (!instance.hasSideEffects()) { instance = program.getLiteralNull(); } JFieldRef fieldRef = new JFieldRef( x.getSourceInfo(), instance, program.getNullField(), x.getEnclosingType(), primitiveTypeOrNullTypeOrArray(program, x.getType())); return fieldRef; }
@Override public boolean visit(JProgram program, Context ctx) { for (Iterator<JDeclaredType> it = program.getDeclaredTypes().iterator(); it.hasNext(); ) { JDeclaredType type = it.next(); if (referencedTypes.contains(type)) { accept(type); } else { prunedMethods.addAll(type.getMethods()); methodsWereRemoved(type.getMethods()); fieldsWereRemoved(type.getFields()); it.remove(); madeChanges(); } } return false; }
@Override public void endVisit(JLocalRef x, Context ctx) { JLocal originalLocal = x.getLocal(); JLocal newLocal = newLocalsByOriginalLocal.get(originalLocal); if (newLocal == null) { newLocal = JProgram.createLocal( originalLocal.getSourceInfo(), originalLocal.getName(), originalLocal.getType(), originalLocal.isFinal(), methodBody); newLocalsByOriginalLocal.put(originalLocal, newLocal); } ctx.replaceMe(new JLocalRef(x.getSourceInfo(), newLocal)); }
private OptimizerStats execImpl(OptimizerContext optimizerCtx) { OptimizerStats stats = new OptimizerStats(NAME); ControlFlowAnalyzer livenessAnalyzer = new ControlFlowAnalyzer(program); livenessAnalyzer.setForPruning(); // SPECIAL: Immortal codegen types are never pruned traverseTypes(livenessAnalyzer, program.immortalCodeGenTypes); if (saveCodeGenTypes) { /* * SPECIAL: Some classes contain methods used by code generation later. * Unless those transforms have already been performed, we must rescue all * contained methods for later user. */ traverseTypes(livenessAnalyzer, program.codeGenTypes); } livenessAnalyzer.traverseEverything(); program.typeOracle.setInstantiatedTypes(livenessAnalyzer.getInstantiatedTypes()); PruneVisitor pruner = new PruneVisitor( livenessAnalyzer.getReferencedTypes(), livenessAnalyzer.getLiveFieldsAndMethods(), optimizerCtx); pruner.accept(program); stats.recordModified(pruner.getNumMods()); if (!pruner.didChange()) { return stats; } CleanupRefsVisitor cleaner = new CleanupRefsVisitor( livenessAnalyzer.getLiveFieldsAndMethods(), pruner.getPriorParametersByMethod(), optimizerCtx); cleaner.accept(program.getDeclaredTypes()); optimizerCtx.incOptimizationStep(); optimizerCtx.syncDeletedSubCallGraphsSince( optimizerCtx.getLastStepFor(NAME) + 1, prunedMethods); JavaAstVerifier.assertProgramIsConsistent(program); return stats; }
private static void addMember( LinkedHashMap<String, LinkedHashMap<String, HasEnclosingType>> matchesBySig, HasEnclosingType member, String refSig) { LinkedHashMap<String, HasEnclosingType> matchesByFullSig = matchesBySig.get(refSig); if (matchesByFullSig == null) { matchesByFullSig = new LinkedHashMap<String, HasEnclosingType>(); matchesBySig.put(refSig, matchesByFullSig); } String fullSig; if (member instanceof JField) { fullSig = ((JField) member).getName(); } else { fullSig = JProgram.getJsniSig((JMethod) member); } matchesByFullSig.put(fullSig, member); }
/** * Choose an initial load sequence of split points for the specified program. Do so by identifying * split points whose code always load first, before any other split points. As a side effect, * modifies {@link com.google.gwt.core.client.impl.AsyncFragmentLoader#initialLoadSequence} in the * program being compiled. * * @throws UnableToCompleteException If the module specifies a bad load order */ public static void pickInitialLoadSequence( TreeLogger logger, JProgram program, ConfigurationProperties config) throws UnableToCompleteException { SpeedTracerLogger.Event codeSplitterEvent = SpeedTracerLogger.start( CompilerEventType.CODE_SPLITTER, "phase", "pickInitialLoadSequence"); TreeLogger branch = logger.branch(TreeLogger.TRACE, "Looking up initial load sequence for split points"); LinkedHashSet<JRunAsync> asyncsInInitialLoadSequence = new LinkedHashSet<JRunAsync>(); List<String> initialSequence = config.getStrings(PROP_INITIAL_SEQUENCE); for (String runAsyncReference : initialSequence) { JRunAsync runAsync = findRunAsync(runAsyncReference, program, branch); if (asyncsInInitialLoadSequence.contains(runAsync)) { branch.log(TreeLogger.ERROR, "Split point specified more than once: " + runAsyncReference); } asyncsInInitialLoadSequence.add(runAsync); } logInitialLoadSequence(logger, asyncsInInitialLoadSequence); installInitialLoadSequenceField(program, asyncsInInitialLoadSequence); program.setInitialAsyncSequence(asyncsInInitialLoadSequence); codeSplitterEvent.end(); }
/** * Installs the initial load sequence into AsyncFragmentLoader.BROWSER_LOADER. The initializer * looks like this: * * <pre> * AsyncFragmentLoader BROWSER_LOADER = makeBrowserLoader(1, new int[]{}); * </pre> * * The second argument (<code>new int[]</code>) gets replaced by an array corresponding to <code> * initialLoadSequence</code>. */ private static void installInitialLoadSequenceField( JProgram program, LinkedHashSet<JRunAsync> initialLoadSequence) { // Arg 1 is initialized in the source as "new int[]{}". JMethodCall call = ReplaceRunAsyncs.getBrowserLoaderConstructor(program); JExpression arg1 = call.getArgs().get(1); assert arg1 instanceof JNewArray; JArrayType arrayType = program.getTypeArray(JPrimitiveType.INT); assert ((JNewArray) arg1).getArrayType() == arrayType; List<JExpression> initializers = new ArrayList<JExpression>(initialLoadSequence.size()); // RunAsyncFramentIndex will later be replaced by the fragment the async is in. // TODO(rluble): this approach is not very clean, ideally the load sequence should be // installed AFTER code splitting when the fragment ids are known; rather than inserting // a placeholder in the AST and patching the ast later. for (JRunAsync runAsync : initialLoadSequence) { initializers.add( new JNumericEntry( call.getSourceInfo(), "RunAsyncFragmentIndex", runAsync.getRunAsyncId())); } JNewArray newArray = JNewArray.createArrayWithInitializers( arg1.getSourceInfo(), arrayType, Lists.newArrayList(initializers)); call.setArg(1, newArray); }
private JMethodCall createClinitCall(JMethodCall x) { JDeclaredType targetType = x.getTarget().getEnclosingType().getClinitTarget(); if (!getCurrentMethod().getEnclosingType().checkClinitTo(targetType)) { // Access from this class to the target class won't trigger a clinit return null; } if (program.isStaticImpl(x.getTarget()) && !x.getTarget().getEnclosingType().isJsoType()) { // No clinit needed; target is really a non-jso instance method. return null; } if (JProgram.isClinit(x.getTarget())) { // This is a clinit call, doesn't need another clinit return null; } JMethod clinit = targetType.getClinitMethod(); // If the clinit is a non-native, empty body we can optimize it out here if (!clinit.isNative() && (((JMethodBody) clinit.getBody())).getStatements().size() == 0) { return null; } return new JMethodCall(x.getSourceInfo(), null, clinit); }
/** * Given the source code to a Java class named <code>test.EntryPoint</code>, compiles it with * emulated stack traces turned on and returns the JavaScript. */ private JsProgram compileClass(String... lines) throws UnableToCompleteException { // Gather the Java source code to compile. final String code = Joiner.on("\n").join(lines); MockResourceOracle sourceOracle = new MockResourceOracle(); sourceOracle.addOrReplace( new MockJavaResource("test.EntryPoint") { @Override public CharSequence getContent() { return code; } }); sourceOracle.add(JavaAstConstructor.getCompilerTypes()); PrecompileTaskOptions options = new PrecompileTaskOptionsImpl(); options.setOutput(JsOutputOption.PRETTY); options.setRunAsyncEnabled(false); CompilerContext context = new CompilerContext.Builder() .options(options) .minimalRebuildCache(new MinimalRebuildCache()) .build(); ConfigurationProperties config = new ConfigurationProperties(Arrays.asList(recordFileNamesProp, recordLineNumbersProp)); CompilationState state = CompilationStateBuilder.buildFrom(logger, context, sourceOracle.getResources(), null); JProgram jProgram = AstConstructor.construct(logger, state, options, config); jProgram.addEntryMethod(findMethod(jProgram, "onModuleLoad")); if (inline) { MethodInliner.exec(jProgram); } CatchBlockNormalizer.exec(jProgram); // Construct the JavaScript AST. // These passes are needed by GenerateJavaScriptAST. ComputeCastabilityInformation.exec(jProgram, false); ImplementCastsAndTypeChecks.exec(jProgram, false); ArrayNormalizer.exec(jProgram); StringTypeMapper typeMapper = new StringTypeMapper(jProgram); ResolveRuntimeTypeReferences.exec(jProgram, typeMapper, TypeOrder.FREQUENCY); Map<StandardSymbolData, JsName> symbolTable = new TreeMap<StandardSymbolData, JsName>(new SymbolData.ClassIdentComparator()); BindingProperty stackMode = new BindingProperty("compiler.stackMode"); stackMode.addDefinedValue(new ConditionNone(), "EMULATED"); PermutationProperties properties = new PermutationProperties( Arrays.asList( new BindingProperties( new BindingProperty[] {stackMode}, new String[] {"EMULATED"}, config))); JsProgram jsProgram = new JsProgram(); JavaToJavaScriptMap jjsmap = GenerateJavaScriptAST.exec( logger, jProgram, jsProgram, context, typeMapper, symbolTable, properties) .getLeft(); // Finally, run the pass we care about. JsStackEmulator.exec(jProgram, jsProgram, properties, jjsmap); return jsProgram; }
public void exec() { accept(jprogram.getIndexedMethod("Enum.name")); }
public EnumNameReplacer(JProgram jprogram, TreeLogger logger) { this.logger = logger; this.jprogram = jprogram; this.enumType = (JClassType) jprogram.getIndexedType("Enum"); this.enumObfuscatedName = jprogram.getIndexedMethod("Enum.obfuscatedName"); }
/** * Find a split point as designated in the {@link #PROP_INITIAL_SEQUENCE} configuration property. */ public static JRunAsync findRunAsync(String refString, JProgram program, TreeLogger branch) throws UnableToCompleteException { SpeedTracerLogger.Event codeSplitterEvent = SpeedTracerLogger.start(CompilerEventType.CODE_SPLITTER, "phase", "findRunAsync"); Multimap<String, JRunAsync> splitPointsByRunAsyncName = computeRunAsyncsByName(program.getRunAsyncs(), false); if (refString.startsWith("@")) { JsniRef jsniRef = JsniRef.parse(refString); if (jsniRef == null) { branch.log( TreeLogger.ERROR, "Badly formatted JSNI reference in " + PROP_INITIAL_SEQUENCE + ": " + refString); throw new UnableToCompleteException(); } final String lookupErrorHolder[] = new String[1]; JNode referent = JsniRefLookup.findJsniRefTarget( jsniRef, program, new JsniRefLookup.ErrorReporter() { @Override public void reportError(String error) { lookupErrorHolder[0] = error; } }); if (referent == null) { TreeLogger resolveLogger = branch.branch(TreeLogger.ERROR, "Could not resolve JSNI reference: " + jsniRef); resolveLogger.log(TreeLogger.ERROR, lookupErrorHolder[0]); throw new UnableToCompleteException(); } if (!(referent instanceof JMethod)) { branch.log(TreeLogger.ERROR, "Not a method: " + referent); throw new UnableToCompleteException(); } JMethod method = (JMethod) referent; String canonicalName = ReplaceRunAsyncs.getImplicitName(method); Collection<JRunAsync> splitPoints = splitPointsByRunAsyncName.get(canonicalName); if (splitPoints == null) { branch.log(TreeLogger.ERROR, "Method does not enclose a runAsync call: " + jsniRef); throw new UnableToCompleteException(); } if (splitPoints.size() > 1) { branch.log( TreeLogger.ERROR, "Method includes multiple runAsync calls, " + "so it's ambiguous which one is meant: " + jsniRef); throw new UnableToCompleteException(); } assert splitPoints.size() == 1; return splitPoints.iterator().next(); } // Assume it's a raw class name Collection<JRunAsync> splitPoints = splitPointsByRunAsyncName.get(refString); if (splitPoints == null || splitPoints.size() == 0) { branch.log(TreeLogger.ERROR, "No runAsync call is labelled with class " + refString); throw new UnableToCompleteException(); } if (splitPoints.size() > 1) { branch.log( TreeLogger.ERROR, "More than one runAsync call is labelled with class " + refString); throw new UnableToCompleteException(); } assert splitPoints.size() == 1; JRunAsync result = splitPoints.iterator().next(); codeSplitterEvent.end(); return result; }
@Override public void endVisit(JTryStatement x, Context ctx) { if (x.getCatchClauses().isEmpty()) { return; } SourceInfo catchInfo = x.getCatchClauses().get(0).getBlock().getSourceInfo(); JLocal exceptionVariable = newExceptionVariable(x.getSourceInfo()); JBlock newCatchBlock = new JBlock(catchInfo); { // $e = Exceptions.wrap($e) JMethodCall call = new JMethodCall(catchInfo, null, wrapMethod); call.addArg(new JLocalRef(catchInfo, exceptionVariable)); newCatchBlock.addStmt( JProgram.createAssignmentStmt( catchInfo, new JLocalRef(catchInfo, exceptionVariable), call)); } /* * Build up a series of if, else if statements to test the type of the * exception object against the types of the user's catch block. Each catch block might have * multiple types in Java 7. * * Go backwards so we can nest the else statements in the correct order! */ // rethrow the current exception if no one caught it. JStatement cur = new JThrowStatement(catchInfo, new JLocalRef(catchInfo, exceptionVariable)); for (int i = x.getCatchClauses().size() - 1; i >= 0; i--) { JTryStatement.CatchClause clause = x.getCatchClauses().get(i); JBlock block = clause.getBlock(); JLocalRef arg = clause.getArg(); List<JType> exceptionsTypes = clause.getTypes(); catchInfo = block.getSourceInfo(); // if ($e instanceof ArgType1 or $e instanceof ArgType2 ...) { // var userVar = $e; <user code> // } // Handle the first Exception type. JExpression ifTest = new JInstanceOf( catchInfo, (JReferenceType) exceptionsTypes.get(0), new JLocalRef(catchInfo, exceptionVariable)); // Handle the rest of the Exception types if any. for (int j = 1; j < exceptionsTypes.size(); j++) { JExpression orExp = new JInstanceOf( catchInfo, (JReferenceType) exceptionsTypes.get(j), new JLocalRef(catchInfo, exceptionVariable)); ifTest = new JBinaryOperation( catchInfo, JPrimitiveType.BOOLEAN, JBinaryOperator.OR, ifTest, orExp); } JDeclarationStatement declaration = new JDeclarationStatement(catchInfo, arg, new JLocalRef(catchInfo, exceptionVariable)); block.addStmt(0, declaration); // nest the previous as an else for me cur = new JIfStatement(catchInfo, ifTest, block, cur); } newCatchBlock.addStmt(cur); // Replace with a single catch block. x.getCatchClauses().clear(); List<JType> newCatchTypes = new ArrayList<JType>(1); newCatchTypes.add(exceptionVariable.getType()); x.getCatchClauses() .add( new JTryStatement.CatchClause( newCatchTypes, new JLocalRef(newCatchBlock.getSourceInfo(), exceptionVariable), newCatchBlock)); }
/** * Look up a JSNI reference. * * @param ref The reference to look up * @param program The program to look up the reference in * @param errorReporter A callback used to indicate the reason for a failed JSNI lookup * @return The item referred to, or <code>null</code> if it could not be found. If the return * value is <code>null</code>, <code>errorReporter</code> will have been invoked. */ public static HasEnclosingType findJsniRefTarget( JsniRef ref, JProgram program, JsniRefLookup.ErrorReporter errorReporter) { String className = ref.className(); JType type = null; if (!className.equals("null")) { type = program.getTypeFromJsniRef(className); if (type == null) { errorReporter.reportError("Unresolvable native reference to type '" + className + "'"); return null; } } if (!ref.isMethod()) { // look for a field String fieldName = ref.memberName(); if (type == null) { if (fieldName.equals("nullField")) { return program.getNullField(); } } else if (fieldName.equals(JsniRef.CLASS)) { JClassLiteral lit = program.getLiteralClass(type); return lit.getField(); } else if (type instanceof JPrimitiveType) { errorReporter.reportError("May not refer to fields on primitive types"); return null; } else if (type instanceof JArrayType) { errorReporter.reportError("May not refer to fields on array types"); return null; } else { for (JField field : ((JDeclaredType) type).getFields()) { if (field.getName().equals(fieldName)) { return field; } } } errorReporter.reportError( "Unresolvable native reference to field '" + fieldName + "' in type '" + className + "'"); return null; } else if (type instanceof JPrimitiveType) { errorReporter.reportError("May not refer to methods on primitive types"); return null; } else { // look for a method LinkedHashMap<String, LinkedHashMap<String, HasEnclosingType>> matchesBySig = new LinkedHashMap<String, LinkedHashMap<String, HasEnclosingType>>(); String methodName = ref.memberName(); String jsniSig = ref.memberSignature(); if (type == null) { if (jsniSig.equals("nullMethod()")) { return program.getNullMethod(); } } else { findMostDerivedMembers(matchesBySig, (JDeclaredType) type, ref.memberName(), true); LinkedHashMap<String, HasEnclosingType> matches = matchesBySig.get(jsniSig); if (matches != null && matches.size() == 1) { /* * Backward compatibility: allow accessing bridge methods with full * qualification */ return matches.values().iterator().next(); } removeSyntheticMembers(matchesBySig); matches = matchesBySig.get(jsniSig); if (matches != null && matches.size() == 1) { return matches.values().iterator().next(); } } // Not found; signal an error if (matchesBySig.isEmpty()) { errorReporter.reportError( "Unresolvable native reference to method '" + methodName + "' in type '" + className + "'"); return null; } else { StringBuilder suggestList = new StringBuilder(); String comma = ""; // use a TreeSet to sort the near matches TreeSet<String> almostMatchSigs = new TreeSet<String>(); for (String sig : matchesBySig.keySet()) { if (matchesBySig.get(sig).size() == 1) { almostMatchSigs.add(sig); } } for (String almost : almostMatchSigs) { suggestList.append(comma + "'" + almost + "'"); comma = ", "; } errorReporter.reportError( "Unresolvable native reference to method '" + methodName + "' in type '" + className + "' (did you mean " + suggestList.toString() + "?)"); return null; } } }
// Arguments for pruned parameters will be pushed right into a multiexpression that will be // evaluated with the next arg, e.g. m(arg1, (prunnedArg2, prunnedArg3, arg4)). private void maybeReplaceForPrunedParameters(JMethodCall x, Context ctx) { if (!priorParametersByMethod.containsKey(x.getTarget())) { // No parameter was pruned. return; } JMethodCall replacementCall = x.cloneWithoutParameters(); assert !x.getTarget().canBePolymorphic(); List<JParameter> originalParams = priorParametersByMethod.get(x.getTarget()); // The method and the call agree in the number of parameters. assert originalParams.size() == x.getArgs().size(); // Traverse the call arguments left to right. SourceInfo sourceInfo = x.getSourceInfo(); JMultiExpression unevaluatedArgumentsForPrunedParameters = new JMultiExpression(sourceInfo); List<JExpression> args = x.getArgs(); for (int currentArgumentIndex = 0; currentArgumentIndex < args.size(); ++currentArgumentIndex) { JExpression arg = args.get(currentArgumentIndex); // If the parameter was not pruned . if (referencedNonTypes.contains(originalParams.get(currentArgumentIndex))) { // Add the current argument to the list of unevaluated arguments and pass the multi // expression to the call. unevaluatedArgumentsForPrunedParameters.addExpressions(arg); replacementCall.addArg(unevaluatedArgumentsForPrunedParameters); // Reset the accumulating multi expression. unevaluatedArgumentsForPrunedParameters = new JMultiExpression(sourceInfo); } else if (arg.hasSideEffects()) { // If the argument was pruned and has sideffects accumulate it; otherwise discard. unevaluatedArgumentsForPrunedParameters.addExpressions(arg); } } if (unevaluatedArgumentsForPrunedParameters.isEmpty()) { // We are done, all (side effectful) parameters have been evaluated. ctx.replaceMe(replacementCall); return; } // If the last few parameters where pruned, we need to evaluate the (side effectful) arguments // for those parameters. if (replacementCall.getArgs().isEmpty()) { // All parameters have been pruned, replace by (prunedArg1, ..., prunedArgn, m()). unevaluatedArgumentsForPrunedParameters.addExpressions(replacementCall); ctx.replaceMe(unevaluatedArgumentsForPrunedParameters); return; } // Some parameters have been pruned from the end, replace by // m(arg1,..., (lastArg = lastUnprunedArg, remainingArgs, lastArg)) JExpression lastArg = Iterables.getLast(replacementCall.getArgs()); JLocal tempVar = createTempLocal( sourceInfo, Iterables.getLast(Iterables.filter(originalParams, Predicates.in(referencedNonTypes))) .getType()); unevaluatedArgumentsForPrunedParameters.addExpressions( 0, JProgram.createAssignment( lastArg.getSourceInfo(), new JLocalRef(sourceInfo, tempVar), lastArg)); unevaluatedArgumentsForPrunedParameters.addExpressions(new JLocalRef(sourceInfo, tempVar)); replacementCall.setArg( replacementCall.getArgs().size() - 1, unevaluatedArgumentsForPrunedParameters); ctx.replaceMe(replacementCall); }