| /* |
| * |
| * 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); |
| } |
| } |
| |
| } |