blob: 91e0d5e4cee519b6f7f54352c208a9c45a1311a8 [file] [log] [blame]
/*
*
* 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.royale.compiler.internal.definitions;
import java.util.List;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.IScopedDefinition;
import org.apache.royale.compiler.definitions.ITypeDefinition;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.scopes.IASScope;
import org.apache.royale.compiler.internal.scopes.ASFileScope;
import org.apache.royale.compiler.internal.scopes.ASScope;
import org.apache.royale.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
* @param favorTypes
* @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, boolean favorTypes)
{
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);
}
// this is used to favor type definitions over function definitions
// when resolving the type of a variable (which can't be a function)
if (resolvedDef == null && defs.size() == 2)
{
IDefinition def0 = defs.get(0);
IDefinition def1 = defs.get(1);
if (def0 instanceof FunctionDefinition &&
(def1 instanceof ClassDefinition ||
(def1 instanceof InterfaceDefinition)))
{
resolvedDef = favorTypes ? def1 : def0;
}
else if (def1 instanceof FunctionDefinition &&
(def0 instanceof ClassDefinition ||
(def0 instanceof InterfaceDefinition)))
{
resolvedDef = favorTypes ? def0 : def1;
}
}
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;
}
}