/* * * 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 static org.apache.flex.compiler.common.ISourceLocation.UNKNOWN; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.apache.flex.compiler.common.DependencyType; import org.apache.flex.compiler.constants.IASKeywordConstants; import org.apache.flex.compiler.constants.IMetaAttributeConstants; import org.apache.flex.compiler.definitions.IClassDefinition; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.definitions.IFunctionDefinition; import org.apache.flex.compiler.definitions.IGetterDefinition; import org.apache.flex.compiler.definitions.IInterfaceDefinition; import org.apache.flex.compiler.definitions.INamespaceDefinition; import org.apache.flex.compiler.definitions.IPackageDefinition; import org.apache.flex.compiler.definitions.IParameterDefinition; import org.apache.flex.compiler.definitions.ISetterDefinition; import org.apache.flex.compiler.definitions.ITypeDefinition; import org.apache.flex.compiler.definitions.references.INamespaceReference; import org.apache.flex.compiler.definitions.metadata.IMetaTag; import org.apache.flex.compiler.definitions.references.IReference; import org.apache.flex.compiler.internal.projects.CompilerProject; import org.apache.flex.compiler.projects.ICompilerProject; import org.apache.flex.compiler.scopes.IASScope; import org.apache.flex.compiler.scopes.IDefinitionSet; import org.apache.flex.compiler.tree.as.IASNode; import org.apache.flex.compiler.tree.as.IContainerNode; import org.apache.flex.compiler.tree.as.IDefinitionNode; import org.apache.flex.compiler.tree.as.IFunctionNode; /** * 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 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); return baseFunc instanceof FunctionDefinition ? (FunctionDefinition)baseFunc : null; } 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 Falcon 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); } } }