/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.devtools.j2objc.translate; import com.google.common.collect.ImmutableSet; import com.google.devtools.j2objc.ast.AnnotationTypeDeclaration; import com.google.devtools.j2objc.ast.ClassInstanceCreation; import com.google.devtools.j2objc.ast.CompilationUnit; import com.google.devtools.j2objc.ast.ConstructorInvocation; import com.google.devtools.j2objc.ast.CreationReference; import com.google.devtools.j2objc.ast.EnumDeclaration; import com.google.devtools.j2objc.ast.Expression; import com.google.devtools.j2objc.ast.ExpressionMethodReference; import com.google.devtools.j2objc.ast.FieldAccess; import com.google.devtools.j2objc.ast.FunctionalExpression; import com.google.devtools.j2objc.ast.LambdaExpression; import com.google.devtools.j2objc.ast.MethodDeclaration; import com.google.devtools.j2objc.ast.MethodInvocation; import com.google.devtools.j2objc.ast.Name; import com.google.devtools.j2objc.ast.QualifiedName; import com.google.devtools.j2objc.ast.SimpleName; import com.google.devtools.j2objc.ast.SingleVariableDeclaration; import com.google.devtools.j2objc.ast.SuperConstructorInvocation; import com.google.devtools.j2objc.ast.SuperFieldAccess; import com.google.devtools.j2objc.ast.SuperMethodInvocation; import com.google.devtools.j2objc.ast.SuperMethodReference; import com.google.devtools.j2objc.ast.ThisExpression; import com.google.devtools.j2objc.ast.TreeUtil; import com.google.devtools.j2objc.ast.Type; import com.google.devtools.j2objc.ast.TypeDeclaration; import com.google.devtools.j2objc.ast.UnitTreeVisitor; import com.google.devtools.j2objc.ast.VariableDeclaration; import com.google.devtools.j2objc.ast.VariableDeclarationFragment; import com.google.devtools.j2objc.util.CaptureInfo; import com.google.devtools.j2objc.util.ElementUtil; import com.google.devtools.j2objc.util.TypeUtil; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.Set; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; /** * Visits a compilation unit and creates variable elements for outer references * and captured local variables where required. Also generates an outer * reference path for any nodes where an outer reference is required. The * generated paths are lists of variable elements for the outer fields that can * be used to reconstruct the given expression. * * OuterReferenceResolver should be run prior to any AST mutations. * * @author Keith Stanger */ public class OuterReferenceResolver extends UnitTreeVisitor { private final CaptureInfo captureInfo; private Scope topScope = null; public OuterReferenceResolver(CompilationUnit unit) { super(unit); this.captureInfo = unit.getEnv().captureInfo(); } private enum ScopeKind { CLASS, LAMBDA, METHOD } /** * Encapsulates relevant information about the types being visited. Scope instances are linked to * form a stack of enclosing types and methods. */ private class Scope { private final ScopeKind kind; private final Scope outer; private final Scope outerClass; // Direct pointer to the next CLASS scope. private final TypeElement type; private final Set<Element> inheritedScope; private final boolean initializingContext; private final Set<VariableElement> declaredVars = new HashSet<>(); // These callbacks are used for correct resolution of local classes where the captures are not // always known at the point of creation. private List<Runnable> onExit = new ArrayList<>(); private final Queue<Runnable> onOuterParam; // The following fields are used only by CLASS scope kinds. private int constructorCount = 0; private int constructorsNotNeedingSuperOuterScope = 0; private Scope(Scope outer, TypeElement type) { kind = ElementUtil.isLambda(type) ? ScopeKind.LAMBDA : ScopeKind.CLASS; this.outer = outer; outerClass = firstClassScope(outer); this.type = type; ImmutableSet.Builder<Element> inheritedScopeBuilder = ImmutableSet.builder(); // Lambdas are ignored when resolving implicit outer scope. if (kind == ScopeKind.CLASS) { typeUtil.visitTypeHierarchy(type.asType(), inheritedType -> { inheritedScopeBuilder.add(inheritedType.asElement()); return true; }); } // If type is an interface, type.getSuperClass() returns null even though all interfaces // "inherit" from Object. Therefore we add this manually to make the set complete. This is // needed because Java 8 default methods can call methods in Object. // TODO(tball): remove when javac update is complete. if (ElementUtil.isInterface(type)) { inheritedScopeBuilder.add(typeUtil.getJavaObject()); } this.inheritedScope = inheritedScopeBuilder.build(); this.initializingContext = kind == ScopeKind.CLASS; this.onOuterParam = new LinkedList<>(); } /** * Creates a Scope for a method declaration. This scope will contain mostly the same state as * its enclosing CLASS scope, but may have a different value for "initializingContext". */ private Scope(Scope outer, ExecutableElement method) { kind = ScopeKind.METHOD; this.outer = outer; // Skip over the immediately enclosing class, since this scope has the same type information. outerClass = outer.outerClass; type = outer.type; inheritedScope = outer.inheritedScope; initializingContext = ElementUtil.isConstructor(method); onOuterParam = outer.onOuterParam; } private boolean isInitializing() { return initializingContext && this == peekScope(); } } private Scope peekScope() { assert topScope != null; return topScope; } private static Scope firstClassScope(Scope scope) { while (scope != null && scope.kind != ScopeKind.CLASS) { scope = scope.outer; } return scope; } // Finds the non-method scope for the given type. private Scope findScopeForType(TypeElement type) { Scope scope = peekScope(); while (scope != null) { if (scope.kind != ScopeKind.METHOD && type.equals(scope.type)) { return scope; } scope = scope.outer; } return null; } private Runnable captureCurrentScope(Runnable runnable) { Scope capturedScope = peekScope(); return new Runnable() { @Override public void run() { Scope saved = topScope; topScope = capturedScope; runnable.run(); topScope = saved; } }; } private void onExitScope(TypeElement type, Runnable runnable) { Scope scope = findScopeForType(type); if (scope != null) { scope.onExit.add(captureCurrentScope(runnable)); } else { // The given type is not currently in scope, so execute the runnable now. runnable.run(); } } // Executes the runnable if or when the given type needs an outer param. private void whenNeedsOuterParam(TypeElement type, Runnable runnable) { if (captureInfo.needsOuterParam(type)) { runnable.run(); } else if (ElementUtil.isLocal(type)) { Scope scope = findScopeForType(type); if (scope != null) { scope.onOuterParam.add(captureCurrentScope(runnable)); } } } private VariableElement getOrCreateOuterVar(Scope scope) { while (!scope.onOuterParam.isEmpty()) { scope.onOuterParam.remove().run(); } return scope.isInitializing() ? captureInfo.getOrCreateOuterParam(scope.type) : captureInfo.getOrCreateOuterField(scope.type); } private VariableElement getOrCreateCaptureVar(VariableElement var, Scope scope) { return scope.isInitializing() ? captureInfo.getOrCreateCaptureParam(var, scope.type) : captureInfo.getOrCreateCaptureField(var, scope.type); } private Name getOuterPath(TypeElement type) { Name path = null; for (Scope scope = peekScope(); !type.equals(scope.type); scope = scope.outerClass) { path = Name.newName(path, getOrCreateOuterVar(scope)); } return path; } private Name getOuterPathInherited(TypeElement type) { Name path = null; for (Scope scope = peekScope(); !scope.inheritedScope.contains(type); scope = scope.outerClass) { path = Name.newName(path, getOrCreateOuterVar(scope)); } return path; } private Name getPathForField(VariableElement var, TypeMirror type) { Name path = getOuterPathInherited((TypeElement) var.getEnclosingElement()); if (path != null) { path = Name.newName(path, var, type); } return path; } private Expression getPathForLocalVar(VariableElement var) { Name path = null; Scope scope = peekScope(); if (scope.declaredVars.contains(var)) { // Var is declared in current scope, return empty path. return path; } if (var.getConstantValue() != null) { // Var has constant value, return a literal. return TreeUtil.newLiteral(var.getConstantValue(), typeUtil); } Scope lastScope = scope; while (!(scope = scope.outer).declaredVars.contains(var)) { // Except for the top scope, only include CLASS scopes when generating the path. if (scope == lastScope.outerClass) { path = Name.newName(path, getOrCreateOuterVar(lastScope)); lastScope = scope; } } return Name.newName(path, getOrCreateCaptureVar(var, lastScope)); } private void pushType(TypeElement type) { topScope = new Scope(topScope, type); } private void popType() { Scope currentScope = peekScope(); topScope = currentScope.outer; for (Runnable runnable : currentScope.onExit) { runnable.run(); } } // Resolve the path for the outer scope to a SuperConstructorInvocation. This path goes on the // type node because there may be implicit super invocations. private void addSuperOuterPath(TypeDeclaration node) { TypeElement superclass = ElementUtil.getSuperclass(node.getTypeElement()); if (superclass != null && captureInfo.needsOuterParam(superclass)) { node.setSuperOuter(getOuterPathInherited(ElementUtil.getDeclaringClass(superclass))); } } private void addCaptureArgs(TypeElement type, List<Expression> args) { for (VariableElement var : captureInfo.getCapturedVars(type)) { Expression path = getPathForLocalVar(var); if (path == null) { path = new SimpleName(var); } args.add(path); } } @Override public boolean visit(TypeDeclaration node) { pushType(node.getTypeElement()); return true; } @Override public void endVisit(TypeDeclaration node) { Scope currentScope = peekScope(); if (currentScope.constructorCount == 0) { // Implicit default constructor. currentScope.constructorCount++; } if (currentScope.constructorCount > currentScope.constructorsNotNeedingSuperOuterScope) { addSuperOuterPath(node); } addCaptureArgs(ElementUtil.getSuperclass(node.getTypeElement()), node.getSuperCaptureArgs()); popType(); } @Override public boolean visit(EnumDeclaration node) { pushType(node.getTypeElement()); return true; } @Override public void endVisit(EnumDeclaration node) { popType(); } @Override public boolean visit(AnnotationTypeDeclaration node) { pushType(node.getTypeElement()); return true; } @Override public void endVisit(AnnotationTypeDeclaration node) { popType(); } private void endVisitFunctionalExpression(FunctionalExpression node) { // Resolve outer and capture arguments. TypeElement typeElement = node.getTypeElement(); if (captureInfo.needsOuterParam(typeElement)) { node.setLambdaOuterArg(getOuterPathInherited(ElementUtil.getDeclaringClass(typeElement))); } addCaptureArgs(typeElement, node.getLambdaCaptureArgs()); } @Override public boolean visit(LambdaExpression node) { pushType(node.getTypeElement()); return true; } @Override public void endVisit(LambdaExpression node) { popType(); endVisitFunctionalExpression(node); } @Override public void endVisit(ExpressionMethodReference node) { Expression target = node.getExpression(); if (!ElementUtil.isStatic(node.getExecutableElement()) && isValue(target)) { captureInfo.addMethodReferenceReceiver(node.getTypeElement(), target.getTypeMirror()); } } private static boolean isValue(Expression expr) { return !(expr instanceof Name) || ElementUtil.isVariable(((Name) expr).getElement()); } @Override public boolean visit(FieldAccess node) { node.getExpression().accept(this); return false; } @Override public boolean visit(SuperFieldAccess node) { VariableElement var = node.getVariableElement(); Name path = getPathForField(var, node.getTypeMirror()); if (path != null) { node.replaceWith(path); } return false; } @Override public boolean visit(QualifiedName node) { node.getQualifier().accept(this); return false; } @Override public boolean visit(SimpleName node) { VariableElement var = TreeUtil.getVariableElement(node); if (var != null) { Expression path = null; if (ElementUtil.isInstanceVar(var)) { path = getPathForField(var, node.getTypeMirror()); } else if (!var.getKind().isField()) { path = getPathForLocalVar(var); } if (path != null) { node.replaceWith(path); } } return true; } @Override public boolean visit(ThisExpression node) { Name qualifier = TreeUtil.remove(node.getQualifier()); if (qualifier != null) { Name path = getOuterPath((TypeElement) qualifier.getElement()); if (path != null) { node.replaceWith(path); } } else { Scope currentScope = peekScope(); if (ElementUtil.isLambda(currentScope.type)) { Name path = getOuterPath(ElementUtil.getDeclaringClass(currentScope.type)); assert path != null : "this keyword within a lambda should have a non-empty path"; node.replaceWith(path); } } return true; } @Override public void endVisit(MethodInvocation node) { ExecutableElement method = node.getExecutableElement(); if (node.getExpression() == null && !ElementUtil.isStatic(method)) { node.setExpression(getOuterPathInherited(ElementUtil.getDeclaringClass(method))); } } private Name getSuperInvocationPath(Name qualifier) { if (qualifier != null) { return getOuterPath((TypeElement) qualifier.getElement()); } else { Scope currentScope = peekScope(); if (ElementUtil.isLambda(currentScope.type)) { return getOuterPath(ElementUtil.getDeclaringClass(currentScope.type)); } } return null; } @Override public void endVisit(SuperMethodInvocation node) { if (ElementUtil.isDefault(node.getExecutableElement())) { // Default methods can be invoked with a SuperMethodInvocation. In this // case the qualifier is not an enclosing class, but the interface that // implements the default method. Since the default method is an instance // method it captures self. Scope currentScope = peekScope(); if (ElementUtil.isLambda(currentScope.type)) { node.setReceiver(getOuterPath(ElementUtil.getDeclaringClass(currentScope.type))); } } else { node.setReceiver(getSuperInvocationPath(node.getQualifier())); } node.setQualifier(null); } @Override public void endVisit(SuperMethodReference node) { TypeElement lambdaType = node.getTypeElement(); pushType(lambdaType); node.setReceiver(getSuperInvocationPath(TreeUtil.remove(node.getQualifier()))); popType(); endVisitFunctionalExpression(node); } @Override public void endVisit(ClassInstanceCreation node) { TypeElement typeElement = (TypeElement) node.getExecutableElement().getEnclosingElement(); if (node.getExpression() == null) { whenNeedsOuterParam(typeElement, () -> { node.setExpression(getOuterPathInherited(ElementUtil.getDeclaringClass(typeElement))); }); } if (ElementUtil.isLocal(typeElement)) { onExitScope(typeElement, () -> { addCaptureArgs(typeElement, node.getCaptureArgs()); }); } } @Override public void endVisit(CreationReference node) { Type typeNode = node.getType(); TypeMirror creationType = typeNode.getTypeMirror(); if (TypeUtil.isArray(creationType)) { // Nothing to capture for array creations. return; } TypeElement lambdaType = node.getTypeElement(); pushType(lambdaType); // This is kind of messy, but we use the Type child node as the key for capture scope to be // transferred to the inner ClassInstanceCreation. The capture scope of the CreationReference // node will be transferred to the ClassInstanceCreation that creates the lambda instance. TypeElement creationElement = TypeUtil.asTypeElement(creationType); whenNeedsOuterParam(creationElement, () -> { TypeElement enclosingTypeElement = ElementUtil.getDeclaringClass(creationElement); node.setCreationOuterArg(getOuterPathInherited(enclosingTypeElement)); }); if (ElementUtil.isLocal(creationElement)) { onExitScope(creationElement, () -> { addCaptureArgs(creationElement, node.getCreationCaptureArgs()); }); } popType(); endVisitFunctionalExpression(node); } private boolean visitVariableDeclaration(VariableDeclaration node) { peekScope().declaredVars.add(node.getVariableElement()); return true; } @Override public boolean visit(VariableDeclarationFragment node) { return visitVariableDeclaration(node); } @Override public boolean visit(SingleVariableDeclaration node) { return visitVariableDeclaration(node); } @Override public boolean visit(MethodDeclaration node) { Scope currentScope = peekScope(); ExecutableElement elem = node.getExecutableElement(); if (ElementUtil.isConstructor(elem)) { currentScope.constructorCount++; } topScope = new Scope(currentScope, elem); return true; } @Override public void endVisit(MethodDeclaration node) { topScope = topScope.outer; } @Override public void endVisit(ConstructorInvocation node) { firstClassScope(peekScope()).constructorsNotNeedingSuperOuterScope++; } @Override public void endVisit(SuperConstructorInvocation node) { if (node.getExpression() != null) { firstClassScope(peekScope()).constructorsNotNeedingSuperOuterScope++; } } }