blob: 9f0e546ed311b1bc450bb463cf28d3a44fc888c8 [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 static org.apache.royale.compiler.common.ISourceLocation.UNKNOWN;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.constants.IASKeywordConstants;
import org.apache.royale.compiler.constants.IMetaAttributeConstants;
import org.apache.royale.compiler.definitions.IClassDefinition;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.IFunctionDefinition;
import org.apache.royale.compiler.definitions.IGetterDefinition;
import org.apache.royale.compiler.definitions.IInterfaceDefinition;
import org.apache.royale.compiler.definitions.INamespaceDefinition;
import org.apache.royale.compiler.definitions.IPackageDefinition;
import org.apache.royale.compiler.definitions.IParameterDefinition;
import org.apache.royale.compiler.definitions.ISetterDefinition;
import org.apache.royale.compiler.definitions.ITypeDefinition;
import org.apache.royale.compiler.definitions.references.INamespaceReference;
import org.apache.royale.compiler.definitions.metadata.IMetaTag;
import org.apache.royale.compiler.definitions.metadata.IMetaTagAttribute;
import org.apache.royale.compiler.definitions.references.IReference;
import org.apache.royale.compiler.internal.definitions.metadata.MetaTag;
import org.apache.royale.compiler.internal.projects.CompilerProject;
import org.apache.royale.compiler.problems.ConflictingDefinitionProblem;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.scopes.IASScope;
import org.apache.royale.compiler.scopes.IDefinitionSet;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IContainerNode;
import org.apache.royale.compiler.tree.as.IDefinitionNode;
import org.apache.royale.compiler.tree.as.IFunctionNode;
import com.google.common.base.Predicate;
/**
* Each instance of this class represents the definition of an ActionScript
* function in the symbol table.
* <p>
* After a function definition is in the symbol table, it should always be
* accessed through the read-only <code>IFunctionDefinition</code> interface.
*/
public class FunctionDefinition extends ScopedDefinitionBase implements IFunctionDefinition
{
private static final ParameterDefinition[] NO_PARAMETERS = new ParameterDefinition[0];
public FunctionDefinition(String name)
{
super(name);
}
private ParameterDefinition[] parameters = NO_PARAMETERS;
// This field is similar to typeReference on DefinitionBase.
// For a function, typeReference is always a reference to the Function class,
// so this field store a reference to the return type.
private IReference returnTypeReference;
@Override
public void setNode(IDefinitionNode node)
{
super.setNode(node);
// If we don't have a name offset, maybe we are an anonymous function
if (getNameStart() == UNKNOWN)
{
if (node instanceof IFunctionNode)
{
IFunctionNode functionNode = (IFunctionNode)node;
if (functionNode.getFunctionClassification() == FunctionClassification.LOCAL)
{
// well, we are a local function without a name. So let's make up a name offset.
// so for something like: function():void
// we want the name offset to be the location right before the open parent of the
// parameter list.
// We will derive this offset by getting the "parameters" node and subtracting one
IContainerNode parametersNode = functionNode.getParametersContainerNode();
int synthesizedNameOffset = parametersNode.getStart();
if (synthesizedNameOffset > 0)
synthesizedNameOffset--;
this.setNameLocation(synthesizedNameOffset, synthesizedNameOffset);
}
}
}
}
@Override
public FunctionClassification getFunctionClassification()
{
IDefinition parent = getParent();
if (parent instanceof IFunctionDefinition)
return FunctionClassification.LOCAL;
if (parent instanceof IClassDefinition)
return FunctionClassification.CLASS_MEMBER;
if (parent instanceof IInterfaceDefinition)
return FunctionClassification.INTERFACE_MEMBER;
if (parent instanceof IPackageDefinition)
return FunctionClassification.PACKAGE_MEMBER;
if (parent == null)
{
if (inPackageNamespace())
return FunctionClassification.PACKAGE_MEMBER;
return FunctionClassification.FILE_MEMBER;
}
assert false;
return null;
}
@Override
public ParameterDefinition[] getParameters()
{
return parameters;
}
@Override
public boolean hasRequiredParameters()
{
for (ParameterDefinition parameter : parameters)
{
if (!(parameter.hasDefaultValue()) && (!parameter.isRest()))
return true;
}
return false;
}
public void setParameters(ParameterDefinition[] value)
{
assert value != null : "setParameters() wants an empty array, not null";
parameters = value;
// Parameters from ABC may be unnamed, so we may have avoided the setContainingScope
// logic in addDefinition().
for (ParameterDefinition p : parameters)
{
p.setContainingScope(this.getContainedScope());
}
}
@Override
public String getReturnTypeAsDisplayString()
{
return returnTypeReference != null ? returnTypeReference.getDisplayString() : "";
}
/**
* Sets a reference to the return type for this function.
*
* @param returnTypeReference An {@link IReference} to a class or interface.
*/
public void setReturnTypeReference(IReference returnTypeReference)
{
this.returnTypeReference = returnTypeReference;
}
@Override
public IReference getReturnTypeReference()
{
return returnTypeReference;
}
@Override
public ITypeDefinition resolveReturnType(ICompilerProject project)
{
// The return type of a constructor is its class,
// not <code>void</code>.
if (isConstructor())
{
IDefinition typeDef = getParent();
if (typeDef instanceof ITypeDefinition)
return (ITypeDefinition)typeDef;
else
return null;
}
// TODO We don't really need to make this a signature dependency
// if this function is a function closure. If this function
// is a closure then we could make this an expression dependency
// instead.
DependencyType dt = DependencyType.SIGNATURE;
return resolveType(returnTypeReference, project, dt);
}
@Override
public boolean isConstructor()
{
return (flags & FLAG_CONSTRUCTOR) != 0;
}
public void setAsConstructor(ClassDefinition classDef)
{
flags |= FLAG_CONSTRUCTOR;
classDef.setConstructor(this);
}
@Override
public boolean isCastFunction()
{
return (flags & FLAG_CAST_FUNCTION) != 0;
}
public void setCastFunction()
{
flags |= FLAG_CAST_FUNCTION;
}
@Override
public boolean inlineFunction()
{
// only attempt to inline a function if it has the inline m
if (!isInline())
return false;
if (canFunctionBeInlined())
return true;
return false;
}
@Override
public final boolean isInline()
{
IMetaTag inlineMetaData = getMetaTagByName(IMetaAttributeConstants.ATTRIBUTE_INLINE);
if (inlineMetaData == null)
return false;
return true;
}
protected final boolean canFunctionBeInlined()
{
// only final or static functions and top level functions can be inlined
if (!(isFinal() || isStatic() || isTopLevelDefinition()))
return false;
// methods on an interface can't be inlined, only the implementation methods
IDefinition containingDef = getContainingScope().getDefinition();
if (containingDef instanceof InterfaceDefinition)
return false;
return true;
}
@Override
public boolean isAbstract()
{
if(super.isAbstract())
{
return true;
}
IMetaTag[] metaTags = getMetaTagsByName(IMetaAttributeConstants.ATTRIBUTE_ABSTRACT);
return metaTags != null && metaTags.length > 0;
}
/**
* Utility to mark a definition as abstract. This method should only ever be
* called during construction or initialization of a definition.
*/
@Override
public void setAbstract()
{
super.setAbstract();
MetaTag abstractMetaTag = new MetaTag(this, IMetaAttributeConstants.ATTRIBUTE_ABSTRACT, new IMetaTagAttribute[0]);
addMetaTag(abstractMetaTag);
}
@Override
public boolean isPrivate()
{
if (super.isPrivate())
{
return true;
}
if (isConstructor())
{
IDefinition parent = getParent();
if (parent == null)
{
return false;
}
// if the construcutor does not have a private namespace, the parent
// class may have [RoyalePrivateConstructor] metadata instead. this
// is how private constructors are stored in bytecode.
IMetaTag[] metaTags = parent.getMetaTagsByName(IMetaAttributeConstants.ATTRIBUTE_PRIVATE_CONSTRUCTOR);
return metaTags != null && metaTags.length > 0;
}
return false;
}
@Override
public boolean overridesAncestor(ICompilerProject project)
{
return (resolveOverriddenFunction(project) != null);
}
@Override
public FunctionDefinition resolveOverriddenFunction(ICompilerProject project)
{
// The function must be a class method.
if (getFunctionClassification() != FunctionClassification.CLASS_MEMBER)
return null;
// Get the method's class.
ClassDefinition cls = (ClassDefinition)getParent();
// Iterate over the superclasses of this method's class.
ClassDefinition base = (ClassDefinition)cls.resolveBaseClass(project);
if (base != null)
{
// Adjust the namespace if this is the protected namespace
INamespaceDefinition namespace = resolveNamespace(project);
if (namespace == null)
return null; // can't resolve namespace, hence overridden function can't be resolved
INamespaceDefinition protectedNS = cls.getProtectedNamespaceReference().resolveNamespaceReference(project);
if (namespace.equals(protectedNS))
namespace = base.getProtectedNamespaceReference().resolveNamespaceReference(project);
// Look for a property with the same name as this function in the base class
// the lookup will search up the inheritance chain, so we don't have to worry about
// walking up the inheritance chain here.
IDefinition baseFunc = base.getContainedScope().getQualifiedPropertyFromDef(
project, base, this.getBaseName(), namespace, false);
if (baseFunc instanceof FunctionDefinition) return (FunctionDefinition)baseFunc;
IDefinition anyDef = base.getContainedScope().getPropertyFromDef(project, base, this.getBaseName(), new PrivatePredicate(!project.getAllowPrivateNameConflicts()), false);
if (anyDef != null) // there might be a variable or a function in a different namespace (private vs protected)
project.getProblems().add(new ConflictingDefinitionProblem(this.getFunctionNode(), this.getBaseName(), anyDef.getParent().getQualifiedName()));
}
return null;
}
@Override
public boolean isImplementation(ICompilerProject project)
{
return resolveImplementedFunction(project) != null;
}
/**
* Return a list of FunctionDefinitions from the base interfaces that this FunctionDefinition overrides.
* We do not allow overriding of Interface methods, but we need to find the methods that would be overriden
* so that we can issue a diagnostic.
*
* This method will only work for FunctionDefinitions that are interface members.
*
* @param project The project to use to resolve references
* @return A list of IFunctionDefinitions from any base interfaces that have the same base name
* as this FunctionDefinition. If there are none, an empty list will be returned
*/
public List<IFunctionDefinition> resolveOverridenInterfaceFunctions(ICompilerProject project)
{
if( getFunctionClassification() != FunctionClassification.INTERFACE_MEMBER )
return Collections.emptyList();
final InterfaceDefinition interf = (InterfaceDefinition)getParent();
// Find the methods from the base interfaces by calling getPropertiesByNameForMemberAccess
// and passing in the namespace set that has only the interface namespaces from the extended interface
// this way we won't find any methods declared in the interface declaring the function
List<IDefinition> funcs = getContainingASScope().getPropertiesByNameForMemberAccess(
(CompilerProject)project,
this.getBaseName(),
interf.getInterfaceNamespaceSet(project, InterfaceDefinition.InterfaceNamespaceSetOptions.DONT_INCLUDE_THIS));
if( funcs.size() == 0 )
return Collections.emptyList();
List<IFunctionDefinition> conflicts = new ArrayList<IFunctionDefinition>(funcs.size());
// Convert to a list of IFunctionDefinitions
for( IDefinition d : funcs )
{
if( d instanceof IFunctionDefinition)
conflicts.add((IFunctionDefinition)d);
}
return conflicts;
}
@Override
public IFunctionDefinition resolveImplementedFunction(ICompilerProject project)
{
// The function must be a class method.
if (getFunctionClassification() != FunctionClassification.CLASS_MEMBER)
return null;
// Get the method's class.
final ClassDefinitionBase cls = (ClassDefinitionBase)getParent();
// Iterate over all the interfaces implemented by this method's class.
final Iterator<IInterfaceDefinition> iter = cls.interfaceIterator(project);
while (iter.hasNext())
{
final IInterfaceDefinition intf = iter.next();
// In each interface, look for a method matching this one.
final IFunctionDefinition f = findMatchingMethod(intf, project);
if (f != null)
return f;
}
return null;
}
// Look in an specified class or interface for a method
// that matches (i.e., has the same name as) this method.
private FunctionDefinition findMatchingMethod(ITypeDefinition type, ICompilerProject project)
{
final String baseName = getBaseName();
final boolean isInterface = type instanceof IInterfaceDefinition;
// Look at the type's local definitions that have the same name as this function.
final IASScope typeScope = type.getContainedScope();
final IDefinitionSet definitionSet = typeScope.getLocalDefinitionSetByName(baseName);
final int n = definitionSet != null ? definitionSet.getSize() : 0;
for (int i = 0; i < n; i++)
{
IDefinition member = definitionSet.getDefinition(i);
// Just look at functions.
if (member instanceof FunctionDefinition)
{
// If one has the same signature as this function, return it.
final FunctionDefinition f = (FunctionDefinition)member;
if (hasSameNameAndSignature(f, isInterface, project))
return f;
}
}
return null;
}
/**
* Compares the signatures of two methods.
*
* @param other is a function to compare "this" to. Must be from a super
* type
* @param otherIsInterface true if "other" is a function from an interface
*/
private boolean hasSameNameAndSignature(IFunctionDefinition other, boolean otherIsInterface, ICompilerProject project)
{
// Compare method names.
String name1 = getBaseName();
String name2 = other.getBaseName();
if (!name1.equals(name2))
return false;
// If other is an interface, then we don't need to compare namespaces.
// As long as the signature match, and the caller guarantees to us that "other" is in fact an
// interface above us in the derivation chain, then we can ignore the namespaces.
// If we did not ignore them, they would not match, since by definition interfaces are in a different
// namespace
if (!otherIsInterface)
{
// Compare method namespaces.
// Note that equals() for namespace references
// actually compares URIs for non-builtin namespaces.
INamespaceReference nsRef1 = getNamespaceReference();
INamespaceReference nsRef2 = other.getNamespaceReference();
if (!nsRef1.equals(nsRef2))
return false;
}
else
{
// you must only call this when "this" is a member of a class
assert (getFunctionClassification() == FunctionClassification.CLASS_MEMBER);
// we can only implement an interface if we are public
if (!NamespaceDefinition.getPublicNamespaceDefinition().equals(this.getNamespaceReference()))
return false;
}
return hasCompatibleSignature(other, project);
}
private boolean copiedMetaData = false;
public boolean hasCompatibleSignature(IFunctionDefinition other, ICompilerProject project)
{
if (!copiedMetaData)
{
if (other.isImplementation(project))
{
copiedMetaData = true;
IMetaTag myTag = this.getMetaTagByName(IMetaAttributeConstants.ATTRIBUTE_SWFOVERRIDE);
if (myTag == null)
{
IMetaTag tag = other.getMetaTagByName(IMetaAttributeConstants.ATTRIBUTE_SWFOVERRIDE);
if (tag != null)
{
this.addMetaTag(tag);
}
}
}
}
// Compare return types.
ITypeDefinition returnType1 = resolveReturnType(project);
ITypeDefinition returnType2 = other.resolveReturnType(project);
if (!project.isCompatibleOverrideReturnType(this, returnType1, returnType2))
return false;
// Compare parameters.
IParameterDefinition[] params1 = getParameters();
IParameterDefinition[] params2 = other.getParameters();
// Compare number of parameters.
int n1 = params1 != null ? params1.length : 0;
int n2 = params2 != null ? params2.length : 0;
if (n1 != n2)
return false;
for (int i = 0; i < n1; i++)
{
IParameterDefinition param1 = params1[i];
IParameterDefinition param2 = params2[i];
// Compare ith parameter types.
// The types must be resolved because one might be
// "Sprite" and the other "flash.display.Sprite".
ITypeDefinition type1 = param1.resolveType(project);
ITypeDefinition type2 = param2.resolveType(project);
if (type1 != type2)
{
if (!project.isCompatibleOverrideParameterType(this, type1, type2, i))
return false;
}
// Compare ith parameter 'rest' flag.
boolean rest1 = param1.isRest();
boolean rest2 = param2.isRest();
if (rest1 != rest2)
return false;
// Compare ith parameter optionality.
boolean hasDefault1 = param1.hasDefaultValue();
boolean hasDefault2 = param2.hasDefaultValue();
if (hasDefault1 != hasDefault2)
return false;
}
// The signatures are the same.
return true;
}
@Override
public IFunctionNode getFunctionNode()
{
IASNode node = getNode();
if (node instanceof IFunctionNode)
return (IFunctionNode)node;
return null;
}
/**
* Is this definition a toplevel definition - constructors are special,
* which is why this is overriden here
*
* @return true if this definition is declared at file scope, or package
* scope.
*/
@Override
public boolean isTopLevelDefinition()
{
// Constructors are toplevel if their class is toplevel
if (isConstructor())
return ((DefinitionBase)getParent()).isTopLevelDefinition();
// Not a constructor, just follow the usual rules
return super.isTopLevelDefinition();
}
// TODO Remove everything below here when Royale has been integrated into Fb and Fc.
@Override
public boolean matches(DefinitionBase definition)
{
boolean match = super.matches(definition);
if (!match)
return false;
IFunctionDefinition functionDefinition = (IFunctionDefinition)definition;
FunctionClassification classification = functionDefinition.getFunctionClassification();
if (classification != getFunctionClassification())
return false;
// Along with local and file member, name offsets needs to be compared for class/interface members also.
// This is required to differentiate members having same name belonging to different class/interface
// within the same AS file - See FBG-3494 for an example.
if (classification == FunctionClassification.LOCAL || classification == FunctionClassification.FILE_MEMBER
|| classification == FunctionClassification.CLASS_MEMBER || classification == FunctionClassification.INTERFACE_MEMBER)
{
if (functionDefinition.getNameStart() != getNameStart() ||
functionDefinition.getNameEnd() != functionDefinition.getNameEnd())
{
return false;
}
}
if (functionDefinition instanceof ISetterDefinition && !(this instanceof ISetterDefinition))
return false;
if (functionDefinition instanceof IGetterDefinition && !(this instanceof IGetterDefinition))
return false;
if (functionDefinition.isConstructor() && !isConstructor())
return false;
//TODO match params?
if (functionDefinition.getParameters().length != getParameters().length)
return false;
return true;
}
/**
* For debugging only. Produces a string such as
* <code>public function f(int, String):void</code>.
*/
@Override
protected void buildInnerString(StringBuilder sb)
{
sb.append(getNamespaceReferenceAsString());
sb.append(' ');
if (isStatic())
{
sb.append(IASKeywordConstants.STATIC);
sb.append(' ');
}
sb.append(IASKeywordConstants.FUNCTION);
sb.append(' ');
sb.append(getBaseName());
sb.append('(');
IParameterDefinition[] params = getParameters();
int n = params != null ? params.length : 0;
for (int i = 0; i < n; i++)
{
sb.append(params[i].toString());
if (i < n - 1)
{
sb.append(',');
sb.append(' ');
}
}
sb.append(')');
String returnType = getReturnTypeAsDisplayString();
if (!returnType.isEmpty())
{
sb.append(':');
sb.append(returnType);
}
}
private static class PrivatePredicate implements Predicate<IDefinition>
{
private boolean findPrivates;
public PrivatePredicate(boolean b)
{
this.findPrivates = b;
}
@Override
public boolean apply(IDefinition definition)
{
if (!definition.isPrivate()) return true;
return findPrivates;
}
@Override
public boolean test(IDefinition input)
{
return apply(input);
}
}
}