@Override public void recordInterfaces( JSType type, JSType relatedType, DisambiguateProperties<JSType>.Property p) { ObjectType objType = ObjectType.cast(type); if (objType != null) { FunctionType constructor; if (objType instanceof FunctionType) { constructor = (FunctionType) objType; } else if (objType instanceof FunctionPrototypeType) { constructor = ((FunctionPrototypeType) objType).getOwnerFunction(); } else { constructor = objType.getConstructor(); } while (constructor != null) { for (ObjectType itype : constructor.getImplementedInterfaces()) { JSType top = getTypeWithProperty(p.name, itype); if (top != null) { p.addType(itype, top, relatedType); } else { recordInterfaces(itype, relatedType, p); } // If this interface invalidated this property, return now. if (p.skipRenaming) return; } if (constructor.isInterface() || constructor.isConstructor()) { constructor = constructor.getSuperClassConstructor(); } else { constructor = null; } } } }
/** Set the prototype without doing any sanity checks. */ private boolean setPrototypeNoCheck(ObjectType prototype, Node propertyNode) { ObjectType oldPrototype = prototypeSlot == null ? null : (ObjectType) prototypeSlot.getType(); boolean replacedPrototype = oldPrototype != null; this.prototypeSlot = new Property("prototype", prototype, true, propertyNode == null ? source : propertyNode); prototype.setOwnerFunction(this); if (oldPrototype != null) { // Disassociating the old prototype makes this easier to debug-- // we don't have to worry about two prototypes running around. oldPrototype.setOwnerFunction(null); } if (isConstructor() || isInterface()) { FunctionType superClass = getSuperClassConstructor(); if (superClass != null) { superClass.addSubType(this); } if (isInterface()) { for (ObjectType interfaceType : getExtendedInterfaces()) { if (interfaceType.getConstructor() != null) { interfaceType.getConstructor().addSubType(this); } } } } if (replacedPrototype) { clearCachedValues(); } return true; }
/** Returns interfaces implemented directly by a class or its superclass. */ public Iterable<ObjectType> getImplementedInterfaces() { FunctionType superCtor = isConstructor() ? getSuperClassConstructor() : null; if (superCtor == null) { return implementedInterfaces; } else { return Iterables.concat(implementedInterfaces, superCtor.getImplementedInterfaces()); } }
public boolean hasImplementedInterfaces() { if (!implementedInterfaces.isEmpty()) { return true; } FunctionType superCtor = isConstructor() ? getSuperClassConstructor() : null; if (superCtor != null) { return superCtor.hasImplementedInterfaces(); } return false; }
@Override public JSType getInstanceFromPrototype(JSType type) { if (type.isFunctionPrototypeType()) { FunctionPrototypeType prototype = (FunctionPrototypeType) type; FunctionType owner = prototype.getOwnerFunction(); if (owner.isConstructor() || owner.isInterface()) { return ((FunctionPrototypeType) type).getOwnerFunction().getInstanceType(); } } return null; }
private void addRelatedExtendedInterfaces(ObjectType instance, Set<ObjectType> set) { FunctionType constructor = instance.getConstructor(); if (constructor != null) { if (!set.add(instance)) { return; } for (ObjectType interfaceType : constructor.getExtendedInterfaces()) { addRelatedExtendedInterfaces(interfaceType, set); } } }
/** * Infer the parameter and return types of a function from the parameter and return types of the * function it is overriding. * * @param oldType The function being overridden. Does nothing if this is null. * @param paramsParent The LP node of the function that we're assigning to. If null, that just * means we're not initializing this to a function literal. */ FunctionTypeBuilder inferFromOverriddenFunction( @Nullable FunctionType oldType, @Nullable Node paramsParent) { if (oldType == null) { return this; } returnType = oldType.getReturnType(); returnTypeInferred = oldType.isReturnTypeInferred(); if (paramsParent == null) { // Not a function literal. parametersNode = oldType.getParametersNode(); if (parametersNode == null) { parametersNode = new FunctionParamBuilder(typeRegistry).build(); } } else { // We're overriding with a function literal. Apply type information // to each parameter of the literal. FunctionParamBuilder paramBuilder = new FunctionParamBuilder(typeRegistry); Iterator<Node> oldParams = oldType.getParameters().iterator(); boolean warnedAboutArgList = false; boolean oldParamsListHitOptArgs = false; for (Node currentParam = paramsParent.getFirstChild(); currentParam != null; currentParam = currentParam.getNext()) { if (oldParams.hasNext()) { Node oldParam = oldParams.next(); Node newParam = paramBuilder.newParameterFromNode(oldParam); oldParamsListHitOptArgs = oldParamsListHitOptArgs || oldParam.isVarArgs() || oldParam.isOptionalArg(); // The subclass method might right its var_args as individual // arguments. if (currentParam.getNext() != null && newParam.isVarArgs()) { newParam.setVarArgs(false); newParam.setOptionalArg(true); } } else { warnedAboutArgList |= addParameter( paramBuilder, typeRegistry.getNativeType(UNKNOWN_TYPE), warnedAboutArgList, codingConvention.isOptionalParameter(currentParam) || oldParamsListHitOptArgs, codingConvention.isVarArgsParameter(currentParam)); } } parametersNode = paramBuilder.build(); } return this; }
/** * When a class B inherits from A and A is annotated as a dict, then B automatically gets the * annotation, even if B's constructor is not explicitly annotated. */ public boolean makesDicts() { if (!isConstructor()) { return false; } if (propAccess == PropAccess.DICT) { return true; } FunctionType superc = getSuperClassConstructor(); if (superc != null && superc.makesDicts()) { setDict(); return true; } return false; }
public void testExternSubTypes() throws Exception { testSame(ALL_NATIVE_EXTERN_TYPES, "", null); List<FunctionType> subtypes = ((ObjectType) getLastCompiler().getTypeRegistry().getType("Error")) .getConstructor() .getSubTypes(); for (FunctionType type : subtypes) { String typeName = type.getInstanceType().toString(); FunctionType typeInRegistry = ((ObjectType) getLastCompiler().getTypeRegistry().getType(typeName)).getConstructor(); assertTrue(typeInRegistry == type); } }
/** Create a new constructor with the parameters and return type stripped. */ public FunctionType cloneWithoutArrowType() { FunctionType result = new FunctionType( registry, getReferenceName(), source, registry.createArrowType(null, null), getInstanceType(), null, true, false); result.setPrototypeBasedOn(getInstanceType()); return result; }
@Override public boolean isSubtype(JSType that) { if (JSType.isSubtypeHelper(this, that)) { return true; } // Union types if (that.isUnionType()) { // The static {@code JSType.isSubtype} check already decomposed // union types, so we don't need to check those again. return false; } // record types if (that.isRecordType()) { return RecordType.isSubtype(this, that.toMaybeRecordType()); } // Interfaces // Find all the interfaces implemented by this class and compare each one // to the interface instance. ObjectType thatObj = that.toObjectType(); FunctionType thatCtor = thatObj == null ? null : thatObj.getConstructor(); if (getConstructor() != null && getConstructor().isInterface()) { for (ObjectType thisInterface : getCtorExtendedInterfaces()) { if (thisInterface.isSubtype(that)) { return true; } } } else if (thatCtor != null && thatCtor.isInterface()) { Iterable<ObjectType> thisInterfaces = getCtorImplementedInterfaces(); for (ObjectType thisInterface : thisInterfaces) { if (thisInterface.isSubtype(that)) { return true; } } } // other prototype based objects if (isUnknownType() || implicitPrototypeChainIsUnknown()) { // If unsure, say 'yes', to avoid spurious warnings. // TODO(user): resolve the prototype chain completely in all cases, // to avoid guessing. return true; } return thatObj != null && isImplicitPrototype(thatObj); }
/** * @return True if n is a function node which is explicitly annotated as returning a nullable * type, other than {?}. */ private static boolean isReturnTypeNullable(Node n) { if (n == null || !n.isFunction()) { return false; } FunctionType functionType = n.getJSType().toMaybeFunctionType(); if (functionType == null) { // If the JSDoc declares a non-function type on a function node, we still shouldn't crash. return false; } JSType returnType = functionType.getReturnType(); if (returnType == null || returnType.isUnknownType() || !returnType.isNullable()) { return false; } JSDocInfo info = NodeUtil.getBestJSDocInfo(n); return info != null && info.hasReturnType(); }
/** * Given a constructor or an interface type and a property, finds the top-most superclass that has * the property defined (including this constructor). */ public ObjectType getTopMostDefiningType(String propertyName) { Preconditions.checkState(isConstructor() || isInterface()); Preconditions.checkArgument(getInstanceType().hasProperty(propertyName)); FunctionType ctor = this; if (isInterface()) { return getTopDefiningInterface(getInstanceType(), propertyName); } ObjectType topInstanceType = null; do { topInstanceType = ctor.getInstanceType(); ctor = ctor.getSuperClassConstructor(); } while (ctor != null && ctor.getPrototype().hasProperty(propertyName)); return topInstanceType; }
@Override public String getReferenceName() { if (className != null) { return className; } else if (ownerFunction != null) { return ownerFunction.getReferenceName() + ".prototype"; } else { return null; } }
@Override public Iterable<JSType> getTypeAlternatives(JSType type) { if (type.isUnionType()) { return ((UnionType) type).getAlternates(); } else { ObjectType objType = type.toObjectType(); if (objType != null && objType.getConstructor() != null && objType.getConstructor().isInterface()) { List<JSType> list = Lists.newArrayList(); for (FunctionType impl : registry.getDirectImplementors(objType)) { list.add(impl.getInstanceType()); } return list; } else { return null; } } }
/** * A function is a subtype of another if their call methods are related via subtyping and {@code * this} is a subtype of {@code that} with regard to the prototype chain. */ @Override public boolean isSubtype(JSType that) { if (JSType.isSubtypeHelper(this, that)) { return true; } if (that.isFunctionType()) { FunctionType other = that.toMaybeFunctionType(); if (other.isInterface()) { // Any function can be assigned to an interface function. return true; } if (isInterface()) { // An interface function cannot be assigned to anything. return false; } // If functionA is a subtype of functionB, then their "this" types // should be contravariant. However, this causes problems because // of the way we enforce overrides. Because function(this:SubFoo) // is not a subtype of function(this:Foo), our override check treats // this as an error. Let's punt on all this for now. // TODO(nicksantos): fix this. boolean treatThisTypesAsCovariant = // An interface 'this'-type is non-restrictive. // In practical terms, if C implements I, and I has a method m, // then any m doesn't necessarily have to C#m's 'this' // type doesn't need to match I. (other.typeOfThis.toObjectType() != null && other.typeOfThis.toObjectType().getConstructor() != null && other.typeOfThis.toObjectType().getConstructor().isInterface()) || // If one of the 'this' types is covariant of the other, // then we'll treat them as covariant (see comment above). other.typeOfThis.isSubtype(this.typeOfThis) || this.typeOfThis.isSubtype(other.typeOfThis); return treatThisTypesAsCovariant && this.call.isSubtype(other.call); } return getNativeType(JSTypeNative.FUNCTION_PROTOTYPE).isSubtype(that); }
/** * Two function types are equal if their signatures match. Since they don't have signatures, two * interfaces are equal if their names match. */ boolean checkFunctionEquivalenceHelper(FunctionType that, EquivalenceMethod eqMethod) { if (isConstructor()) { if (that.isConstructor()) { return this == that; } return false; } if (isInterface()) { if (that.isInterface()) { return getReferenceName().equals(that.getReferenceName()); } return false; } if (that.isInterface()) { return false; } return typeOfThis.checkEquivalenceHelper(that.typeOfThis, eqMethod) && call.checkArrowEquivalenceHelper(that.call, eqMethod); }
@Override public void clearCachedValues() { super.clearCachedValues(); if (subTypes != null) { for (FunctionType subType : subTypes) { subType.clearCachedValues(); } } if (!isNativeObjectType()) { if (hasInstanceType()) { getInstanceType().clearCachedValues(); } if (prototypeSlot != null) { ((ObjectType) prototypeSlot.getType()).clearCachedValues(); } } }
/** * Infer the role of the function (whether it's a constructor or interface) and what it inherits * from in JSDocInfo. */ FunctionTypeBuilder inferInheritance(@Nullable JSDocInfo info) { if (info != null) { isConstructor = info.isConstructor(); isInterface = info.isInterface(); // base type if (info.hasBaseType()) { if (isConstructor || isInterface) { JSType maybeBaseType = info.getBaseType().evaluate(scope, typeRegistry); if (maybeBaseType != null && maybeBaseType.setValidator(new ExtendedTypeValidator())) { baseType = (ObjectType) maybeBaseType; } } else { reportWarning(EXTENDS_WITHOUT_TYPEDEF, fnName); } } // implemented interfaces if (isConstructor || isInterface) { implementedInterfaces = Lists.newArrayList(); for (JSTypeExpression t : info.getImplementedInterfaces()) { JSType maybeInterType = t.evaluate(scope, typeRegistry); if (maybeInterType != null && maybeInterType.setValidator(new ImplementedTypeValidator())) { implementedInterfaces.add((ObjectType) maybeInterType); } } if (baseType != null) { JSType maybeFunctionType = baseType.getConstructor(); if (maybeFunctionType instanceof FunctionType) { FunctionType functionType = baseType.getConstructor(); Iterables.addAll(implementedInterfaces, functionType.getImplementedInterfaces()); } } } else if (info.getImplementedInterfaceCount() > 0) { reportWarning(IMPLEMENTS_WITHOUT_CONSTRUCTOR, fnName); } } return this; }
/** * Resolves a named type by looking up its first component in the scope, and subsequent components * as properties. The scope must have been fully parsed and a symbol table constructed. */ private void resolveViaProperties(ErrorReporter reporter, StaticTypedScope<JSType> enclosing) { JSType value = lookupViaProperties(reporter, enclosing); // last component of the chain if (value != null && value.isFunctionType() && (value.isConstructor() || value.isInterface())) { FunctionType functionType = value.toMaybeFunctionType(); setReferencedAndResolvedType(functionType.getInstanceType(), reporter); } else if (value != null && value.isNoObjectType()) { setReferencedAndResolvedType( registry.getNativeObjectType(JSTypeNative.NO_OBJECT_TYPE), reporter); } else if (value instanceof EnumType) { setReferencedAndResolvedType(((EnumType) value).getElementsType(), reporter); } else { // We've been running into issues where people forward-declare // non-named types. (This is legitimate...our dependency management // code doubles as our forward-declaration code.) // // So if the type does resolve to an actual value, but it's not named, // then don't respect the forward declaration. handleUnresolvedType(reporter, value == null || value.isUnknownType()); } }
/** Builds the function type, and puts it in the registry. */ FunctionType buildAndRegister() { if (returnType == null) { returnType = typeRegistry.getNativeType(UNKNOWN_TYPE); } if (parametersNode == null) { throw new IllegalStateException("All Function types must have params and a return type"); } FunctionType fnType; if (isConstructor) { fnType = getOrCreateConstructor(); } else if (isInterface) { fnType = typeRegistry.createInterfaceType(fnName, sourceNode); if (getScopeDeclaredIn().isGlobal() && !fnName.isEmpty()) { typeRegistry.declareType(fnName, fnType.getInstanceType()); } maybeSetBaseType(fnType); } else { fnType = new FunctionBuilder(typeRegistry) .withName(fnName) .withSourceNode(sourceNode) .withParamsNode(parametersNode) .withReturnType(returnType, returnTypeInferred) .withTypeOfThis(thisType) .withTemplateName(templateTypeName) .build(); maybeSetBaseType(fnType); } if (implementedInterfaces != null) { fnType.setImplementedInterfaces(implementedInterfaces); } typeRegistry.clearTemplateTypeName(); return fnType; }
/** * Returns a constructor function either by returning it from the registry if it exists or * creating and registering a new type. If there is already a type, then warn if the existing type * is different than the one we are creating, though still return the existing function if * possible. The primary purpose of this is that registering a constructor will fail for all * built-in types that are initialized in {@link JSTypeRegistry}. We a) want to make sure that the * type information specified in the externs file matches what is in the registry and b) annotate * the externs with the {@link JSType} from the registry so that there are not two separate JSType * objects for one type. */ private FunctionType getOrCreateConstructor() { FunctionType fnType = typeRegistry.createConstructorType(fnName, sourceNode, parametersNode, returnType); JSType existingType = typeRegistry.getType(fnName); if (existingType != null) { boolean isInstanceObject = existingType instanceof InstanceObjectType; if (isInstanceObject || fnName.equals("Function")) { FunctionType existingFn = isInstanceObject ? ((InstanceObjectType) existingType).getConstructor() : typeRegistry.getNativeFunctionType(FUNCTION_FUNCTION_TYPE); if (existingFn.getSource() == null) { existingFn.setSource(sourceNode); } if (!existingFn.hasEqualCallType(fnType)) { reportWarning(TYPE_REDEFINITION, fnName, fnType.toString(), existingFn.toString()); } return existingFn; } else { // We fall through and return the created type, even though it will fail // to register. We have no choice as we have to return a function. We // issue an error elsewhere though, so the user should fix it. } } maybeSetBaseType(fnType); if (getScopeDeclaredIn().isGlobal() && !fnName.isEmpty()) { typeRegistry.declareType(fnName, fnType.getInstanceType()); } return fnType; }
/** @param fnNode A node for a function for which to generate a type annotation */ private String getFunctionAnnotation(Node fnNode) { Preconditions.checkState(fnNode.isFunction()); StringBuilder sb = new StringBuilder("/**\n"); JSType type = fnNode.getJSType(); if (type == null || type.isUnknownType()) { return ""; } FunctionType funType = type.toMaybeFunctionType(); // We need to use the child nodes of the function as the nodes for the // parameters of the function type do not have the real parameter names. // FUNCTION // NAME // LP // NAME param1 // NAME param2 if (fnNode != null) { Node paramNode = NodeUtil.getFunctionParameters(fnNode).getFirstChild(); // Param types for (Node n : funType.getParameters()) { // Bail out if the paramNode is not there. if (paramNode == null) { break; } sb.append(" * "); appendAnnotation(sb, "param", getParameterNodeJSDocType(n)); sb.append(" ").append(paramNode.getString()).append("\n"); paramNode = paramNode.getNext(); } } // Return type JSType retType = funType.getReturnType(); if (retType != null && !retType.isUnknownType() && !retType.isEmptyType()) { sb.append(" * "); appendAnnotation(sb, "return", retType.toAnnotationString()); sb.append("\n"); } // Constructor/interface if (funType.isConstructor() || funType.isInterface()) { FunctionType superConstructor = funType.getSuperClassConstructor(); if (superConstructor != null) { ObjectType superInstance = funType.getSuperClassConstructor().getInstanceType(); if (!superInstance.toString().equals("Object")) { sb.append(" * "); appendAnnotation(sb, "extends", superInstance.toAnnotationString()); sb.append("\n"); } } if (funType.isInterface()) { for (ObjectType interfaceType : funType.getExtendedInterfaces()) { sb.append(" * "); appendAnnotation(sb, "extends", interfaceType.toAnnotationString()); sb.append("\n"); } } // Avoid duplicates, add implemented type to a set first Set<String> interfaces = Sets.newTreeSet(); for (ObjectType interfaze : funType.getImplementedInterfaces()) { interfaces.add(interfaze.toAnnotationString()); } for (String interfaze : interfaces) { sb.append(" * "); appendAnnotation(sb, "implements", interfaze); sb.append("\n"); } if (funType.isConstructor()) { sb.append(" * @constructor\n"); } else if (funType.isInterface()) { sb.append(" * @interface\n"); } } if (fnNode != null && fnNode.getBooleanProp(Node.IS_DISPATCHER)) { sb.append(" * @javadispatch\n"); } sb.append(" */\n"); return sb.toString(); }
/** * Computes the supremum or infimum of two functions. Because sup() and inf() share a lot of logic * for functions, we use a single helper. * * @param leastSuper If true, compute the supremum of {@code this} with {@code that}. Otherwise, * compute the infimum. * @return The least supertype or greatest subtype. */ FunctionType supAndInfHelper(FunctionType that, boolean leastSuper) { // NOTE(nicksantos): When we remove the unknown type, the function types // form a lattice with the universal constructor at the top of the lattice, // and the LEAST_FUNCTION_TYPE type at the bottom of the lattice. // // When we introduce the unknown type, it's much more difficult to make // heads or tails of the partial ordering of types, because there's no // clear hierarchy between the different components (parameter types and // return types) in the ArrowType. // // Rather than make the situation more complicated by introducing new // types (like unions of functions), we just fallback on the simpler // approach of getting things right at the top and the bottom of the // lattice. // // If there are unknown parameters or return types making things // ambiguous, then sup(A, B) is always the top function type, and // inf(A, B) is always the bottom function type. Preconditions.checkNotNull(that); if (isEquivalentTo(that)) { return this; } // If these are ordinary functions, then merge them. // Don't do this if any of the params/return // values are unknown, because then there will be cycles in // their local lattice and they will merge in weird ways. if (isOrdinaryFunction() && that.isOrdinaryFunction() && !this.call.hasUnknownParamsOrReturn() && !that.call.hasUnknownParamsOrReturn()) { // Check for the degenerate case, but double check // that there's not a cycle. boolean isSubtypeOfThat = isSubtype(that); boolean isSubtypeOfThis = that.isSubtype(this); if (isSubtypeOfThat && !isSubtypeOfThis) { return leastSuper ? that : this; } else if (isSubtypeOfThis && !isSubtypeOfThat) { return leastSuper ? this : that; } // Merge the two functions component-wise. FunctionType merged = tryMergeFunctionPiecewise(that, leastSuper); if (merged != null) { return merged; } } // The function instance type is a special case // that lives above the rest of the lattice. JSType functionInstance = registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE); if (functionInstance.isEquivalentTo(that)) { return leastSuper ? that : this; } else if (functionInstance.isEquivalentTo(this)) { return leastSuper ? this : that; } // In theory, we should be using the GREATEST_FUNCTION_TYPE as the // greatest function. In practice, we don't because it's way too // broad. The greatest function takes var_args None parameters, which // means that all parameters register a type warning. // // Instead, we use the U2U ctor type, which has unknown type args. FunctionType greatestFn = registry.getNativeFunctionType(JSTypeNative.U2U_CONSTRUCTOR_TYPE); FunctionType leastFn = registry.getNativeFunctionType(JSTypeNative.LEAST_FUNCTION_TYPE); return leastSuper ? greatestFn : leastFn; }
private void maybeSetBaseType(FunctionType fnType) { if (baseType != null) { fnType.setPrototypeBasedOn(baseType); } }