/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.flex.compiler.internal.definitions; import java.util.List; import org.apache.flex.compiler.constants.IASLanguageConstants; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.definitions.IScopedDefinition; import org.apache.flex.compiler.definitions.ITypeDefinition; import org.apache.flex.compiler.projects.ICompilerProject; import org.apache.flex.compiler.scopes.IASScope; import org.apache.flex.compiler.internal.scopes.ASFileScope; import org.apache.flex.compiler.internal.scopes.ASScope; import org.apache.flex.compiler.internal.semantics.SemanticUtils; /** * IDefinition marker to represent ambiguous results. TODO: Can modify to keep * track of what the ambiguous results were. This would be an improvement over * TODO: ASC, which only reported that a reference was ambiguous, but didn't * tell you which symbols it matched. */ public final class AmbiguousDefinition extends DefinitionBase implements IDefinition { private static AmbiguousDefinition def = new AmbiguousDefinition(); /** * Is the definition passed in an AmbiguousDefinition. * * @param d the definition to check * @return true if d is an AmbiguousDefinition */ public static boolean isAmbiguous(IDefinition d) { return d == def; } /** * Get the AmbiguousDefinition instance - currently there is only 1 * AmbiguousDefinition instance, but we may modify that if each * AmbiguousDefinition needs to keep track of it's ambiguous results */ public static AmbiguousDefinition get() { return def; } /** * Constructor. */ private AmbiguousDefinition() { super(""); } /** * Helper method to resolve apparently ambiguous results - there are 2 cases * where we what looks like an ambiguity is not - getter/setter pairs, and * legally re-declared local variables If we have a getter and a setter, we * will end up with 2 resulting definitions, but we don't want to report * them as ambiguous if they are a getter & setter for the same property. in * this case, this method arbitrarily returns the first definition passed in * - this should be ok because getter and setter definitions have methods to * access the other one (resolveCorrespondingAccessor), so callers can use * that to get at the one they want. If we have the same variable declared * multiple times, then we don't want to report access to that variable as * ambiguous. A variable can be legally re-declared when: 1. It is declared * at global scope (outside a package), or it is a local var in a function. * 2. All of the declarations of that variable are declared with the same * type, or '*'. One of the VariableDefinitions with the specific type will * be returned so that the correct type is used by the compiler e.g. if a * Variable is declared twice, once as * and once as String, the result of * this method will be the VariableDefinition typed as String. If all the * VariableDefinitions are declared with the same type, then one will be * arbitrarily returned. * * @param project The Project to use to resolve things * @param defs an Array of definitions to compare * @return the definition to use as the result of the lookup, if the * ambiguity was successfully resolved, otherwise null */ public static IDefinition resolveAmbiguities(ICompilerProject project, List<IDefinition> defs) { IDefinition resolvedDef = null; assert defs.size() > 1 : "This method should only be called when there is an ambiguity to resolve!"; // If we have exactly two definition to choose from, the // two definitions might be a getter/setter pair. if (SemanticUtils.isGetterSetterPair(defs, project)) { resolvedDef = defs.get(0); } if (resolvedDef == null) { // check for redeclared variables and functions resolvedDef = defs.get(0); assert resolvedDef.isInProject(project); List<IDefinition> defsAfterFirst = defs.subList(1, defs.size()); // The result of this loop will either be the VariableDefinition to return if all the ambiguities were // resolved, or null if the ambiguities could not be resolved. for (IDefinition d : defsAfterFirst) { assert d.isInProject(project); if ( resolvedDef instanceof VariableDefinition ) { if( d instanceof VariableDefinition ) resolvedDef = resolveAmbiguousVariableDefinitions(project, resolvedDef, d); else if( d instanceof FunctionDefinition ) resolvedDef = resolveAmbiguousFunctionVariableDefinitions(project, resolvedDef, d); else resolvedDef = null; } else if( resolvedDef instanceof FunctionDefinition ) { if( d instanceof FunctionDefinition ) resolvedDef = resolveAmbiguousFunctionDefinitions(project, resolvedDef, d); else if( d instanceof VariableDefinition ) resolvedDef = resolveAmbiguousFunctionVariableDefinitions(project, resolvedDef, d); else resolvedDef = null; } else { resolvedDef = null; // At least one definition was not a variable definition, so just bail } if (resolvedDef == null) break; } } return resolvedDef; } /** * Determine if two definitions have the same name, namespace qualifier, and * containing scope. * * @param project {@link ICompilerProject} used to resolve namespace * references. * @param d1 first {@link IDefinition} to compare. * @param d2 second {@link IDefinition} to compare. * @return true if both of the specified {@link IDefinition}'s have the same * name, namespace qualifier, and containing scope, false otherwise. */ private static boolean namesAndContainingScopeMatch(ICompilerProject project, IDefinition d1, IDefinition d2) { if (!d1.getBaseName().equals(d2.getBaseName())) return false; if (!d1.resolveNamespace(project).equals(d2.resolveNamespace(project))) return false; if (d1.getContainingScope() != d2.getContainingScope()) return false; return true; } /** * Helper method to try and resolve 2 potentially ambiguous * VariableDefinitions. If the definitions are not actually ambiguous, then * the Definition with the more specific type will be returned. For more * detail see the comments for resolveAmbiguities above */ private static IDefinition resolveAmbiguousVariableDefinitions(ICompilerProject project, IDefinition v1, IDefinition v2) { assert (v1 instanceof VariableDefinition) && (v2 instanceof VariableDefinition); // Make sure they have the same name, and namespace, otherwise they can't possible be a re-decl if (!namesAndContainingScopeMatch(project, v1, v2)) return null; // const definitions always conflict. if (v1 instanceof ConstantDefinition || v2 instanceof ConstantDefinition) return null; IASScope containingScope = v1.getContainingScope(); IScopedDefinition containingDef = v1.getContainingScope().getDefinition(); if (containingDef instanceof FunctionDefinition || containingScope instanceof ASFileScope) { // Only global (outside a package) or function locals can be redeclared. VariableDefinition var1 = (VariableDefinition)v1; VariableDefinition var2 = (VariableDefinition)v2; ITypeDefinition thisType = var1.resolveType(project); ITypeDefinition thatType = var2.resolveType(project); // If the types match, doesn't matter which one we return if (thisType == thatType) { return v1; } else { // If the types don't match, the re-decl is only allowed if one of them is '*' and the other // one is a specific type. In this case, return the def with the more specific type, as that // type will be the type of the var throughout the function IDefinition anyType = project.getBuiltinType(IASLanguageConstants.BuiltinType.ANY_TYPE); if (thisType == anyType) return v2; if (thatType == anyType) return v1; } } return null; } /** * Helper method to try and resolve 2 potentially ambiguous * FunctionDefinitions. If the definitions are not actually ambiguous, then * the Definition with the more specific type will be returned. For more * detail see the comments for resolveAmbiguities above */ private static IDefinition resolveAmbiguousFunctionDefinitions(ICompilerProject project, IDefinition f1, IDefinition f2) { assert (f1 instanceof FunctionDefinition) && (f2 instanceof FunctionDefinition); // Make sure they have the same name, and namespace, otherwise they can't possible be a re-decl if (!namesAndContainingScopeMatch(project, f1, f2)) return null; ASScope containingScope = (ASScope)f1.getContainingScope(); if ((containingScope instanceof ASFileScope) || (containingScope.getContainingDefinition() instanceof FunctionDefinition)) { // All function declarations match, because functions // declared outside of a package are really variables of // type * that are initialized to function closures. int thisStartAbsoluteOffset = f1.getAbsoluteStart(); int thatStartAbsoluteOffset = f2.getAbsoluteStart(); // We'll pretend last definition wins, because that is more like JavaScript. if (thisStartAbsoluteOffset >= thatStartAbsoluteOffset) return f1; else return f2; } return null; } /** * Helper method to try and resolve 2 potentially ambiguous Function and Variable definitions. If the definitions * are not actually ambiguous, the Function definition will be returned, as it will always have the most specific type ('Function'). * * This method must be called with 1 variable definition, and 1 function definition - the order is not important. * * @param project The project to resolve things in * @param f1 The first definition * @param f2 The second definition * @return The Function definition if the ambiguity can be resolve, otherwise null */ private static IDefinition resolveAmbiguousFunctionVariableDefinitions(ICompilerProject project, IDefinition f1, IDefinition f2) { VariableDefinition varDef = null; FunctionDefinition funcDef = null; if(f1 instanceof VariableDefinition ) { assert f2 instanceof FunctionDefinition; varDef = (VariableDefinition)f1; funcDef = (FunctionDefinition)f2; } else { assert f1 instanceof FunctionDefinition && f2 instanceof VariableDefinition; varDef = (VariableDefinition)f2; funcDef = (FunctionDefinition)f1; } // Make sure they have the same name, and namespace, otherwise they can't possible be a re-decl if (!namesAndContainingScopeMatch(project, varDef, funcDef)) return null; // const definitions always conflict. // getters/setters always conlflict with vars if (varDef instanceof ConstantDefinition || funcDef instanceof AccessorDefinition) return null; IASScope containingScope = varDef.getContainingScope(); IScopedDefinition containingDef = varDef.getContainingScope().getDefinition(); if (containingDef instanceof FunctionDefinition || containingScope instanceof ASFileScope) { // Only global (outside a package) or function locals can be redeclared. ITypeDefinition varType = varDef.resolveType(project); IDefinition funcType = project.getBuiltinType(IASLanguageConstants.BuiltinType.FUNCTION); // If the types match, doesn't matter which one we return if (varType == funcType) { return funcDef; } else { // If the types don't match, the re-decl is only allowed if the var type is '*' (the Function type is 'Function'). // In this case, return the def with the more specific type, which will be the function IDefinition anyType = project.getBuiltinType(IASLanguageConstants.BuiltinType.ANY_TYPE); if (varType == anyType) return funcDef; } } return null; } @Override public boolean isInProject(ICompilerProject project) { // The ambiguous definition singleton is logically in all projects. return true; } }