@Nullable private JSType getTypeOfThisForFunctionNode(Node functionNode) { if (isAddingTypes()) { FunctionType functionType = getFunctionType(functionNode); return checkNotNull(functionType.getTypeOfThis(), functionType); } else { return null; // not adding type information } }
@Override public TypeI convertMethodToFunction() { List<JSType> paramTypes = new ArrayList<JSType>(); paramTypes.add(getTypeOfThis()); for (Node param : getParameters()) { paramTypes.add(param.getJSType()); } return registry.createFunctionTypeWithInstanceType( registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE), getReturnType(), paramTypes); }
private JSType convertMethodToFunction(FunctionType method) { List<JSType> paramTypes = new ArrayList<>(); paramTypes.add(method.getTypeOfThis()); for (Node param : method.getParameters()) { paramTypes.add(param.getJSType()); } ObjectType unknown = compiler.getTypeRegistry().getNativeObjectType(JSTypeNative.UNKNOWN_TYPE); return compiler.getTypeRegistry().createFunctionTypeWithInstanceType( unknown, method.getReturnType(), paramTypes); }
private JSType getTypeOfThisForConstructor(Node constructor) { checkArgument(constructor.isFunction(), constructor); // If typechecking has run, all function nodes should have a JSType. Nodes that were in a CAST // will also have the TYPE_BEFORE_CAST property, which is null for other nodes. final JSType constructorTypeBeforeCast = constructor.getJSTypeBeforeCast(); final JSType constructorType = constructorTypeBeforeCast != null ? constructorTypeBeforeCast : constructor.getJSType(); if (constructorType == null) { return null; // Type checking passes must not have run. } checkState(constructorType.isFunctionType()); return constructorType.toMaybeFunctionType().getTypeOfThis(); }
/** * Take the current function type, and try to match the expected function * type. This is a form of backwards-inference, like record-type constraint * matching. */ private FunctionType matchFunction( FunctionType expectedType, FunctionType currentType, boolean declared) { if (declared) { // If the function was declared but it doesn't have a known "this" // but the expected type does, back fill it. if (currentType.getTypeOfThis().isUnknownType() && !expectedType.getTypeOfThis().isUnknownType()) { FunctionType replacement = new FunctionBuilder(registry) .copyFromOtherFunction(currentType) .withTypeOfThis(expectedType.getTypeOfThis()) .build(); return replacement; } } else { // For now, we just make sure the current type has enough // arguments to match the expected type, and return the // expected type if it does. if (currentType.getMaxArity() <= expectedType.getMaxArity()) { return expectedType; } } return currentType; }
/** * Take the current function type, and try to match the expected function * type. This is a form of backwards-inference, like record-type constraint * matching. */ private FunctionType matchFunction( FunctionType expectedType, FunctionType currentType, boolean declared) { if (declared) { // If the function was declared but it doesn't have a known "this" // but the expected type does, back fill it. if (currentType.getTypeOfThis().isUnknownType() && !expectedType.getTypeOfThis().isUnknownType()) { FunctionType replacement = new FunctionBuilder(registry) .copyFromOtherFunction(currentType) .withTypeOfThis(expectedType.getTypeOfThis()) .build(); return replacement; } } else { // For now, we just make sure the current type has enough // arguments to match the expected type, and return the // expected type if it does. if (currentType.getMaxArguments() <= expectedType.getMaxArguments()) { return expectedType; } } return currentType; }
private boolean isPrototypeMethod(TypedVar other) { if (other.getType() != null && other.getType().isOrdinaryFunction()) { JSType typeOfThis = ((FunctionType) other.getType()).getTypeOfThis(); if (typeOfThis != null && !typeOfThis.isUnknownType()) { return true; } } return false; }
/** * Emit a this parameter like `func(this: Foo)` in a function parameters. * * <p>TODO: emit for non-templatized this like `function(this: HTMLElement)` */ private void emitThisParameter(FunctionType ftype, Iterator<Node> parameters) { final JSType typeOfThis = ftype.getTypeOfThis(); // Don't emit for a constructor like `function(new: T)`. // A `this` parameter in a constructor is not allowed in TypeScript. if (typeOfThis == null || ftype.isConstructor()) { return; } final JSDocInfo jsDocInfo = ftype.getJSDocInfo(); // Emit for templatized this param like `function(this: T)` or JSDoc `@this` type. if (!typeOfThis.isTemplateType() && (jsDocInfo == null || jsDocInfo.getThisType() == null)) { return; } emitNoSpace("this :"); visitType(typeOfThis); if (parameters.hasNext()) { emit(", "); } }
@Nullable private JSType getTypeOfThis(Node scopeRoot) { if (scopeRoot.isRoot() || scopeRoot.isScript()) { return castToObject(scopeRoot.getJSType()); } checkArgument(scopeRoot.isFunction(), scopeRoot); JSType nodeType = scopeRoot.getJSType(); if (nodeType != null && nodeType.isFunctionType()) { return nodeType.toMaybeFunctionType().getTypeOfThis(); } else { // Executed when the current scope has not been typechecked. return null; } }
/** * Gets the type of {@code this} in the current scope. */ @Override public JSType getTypeOfThis() { if (isGlobal()) { return ObjectType.cast(rootNode.getJSType()); } Preconditions.checkState(rootNode.isFunction()); JSType nodeType = rootNode.getJSType(); if (nodeType != null && nodeType.isFunctionType()) { return nodeType.toMaybeFunctionType().getTypeOfThis(); } else { // Executed when the current scope has not been typechecked. return null; } }
private ObjectType getThisTypeForCollectingProperties() { Node rootNode = scope.getRootNode(); if (rootNode.isFromExterns()) return null; JSType type = rootNode.getJSType(); if (type == null || !type.isFunctionType()) return null; FunctionType fnType = type.toMaybeFunctionType(); JSType fnThisType = fnType.getTypeOfThis(); return fnThisType.isUnknownType() ? null : fnThisType.toObjectType(); }
/** * Special handling for simple typing returning polymorphic this type in TypeScript. Prefer * `func(): this` instead of `func<T>(this: T): T` when any params are not templatized. */ private boolean shouldSkipEmittingThisTemplateAndParam(FunctionType ftype) { final JSType typeOfThis = ftype.getTypeOfThis(); if (typeOfThis == null || !typeOfThis.isTemplateType() || !typeOfThis.equals(ftype.getReturnType())) { return false; } Iterator<Node> parameters = ftype.getParameters().iterator(); while (parameters.hasNext()) { final JSType paramType = parameters.next().getJSType(); if (!paramType.isTemplatizedType()) { continue; } final TemplateTypeMap templateTypeMap = paramType.getTemplateTypeMap(); for (TemplateType key : templateTypeMap.getTemplateKeys()) { if (templateTypeMap.getResolvedTemplateType(key).equals(typeOfThis)) { return false; } } } return true; }
/** Copies all the information from another function type. */ public FunctionBuilder copyFromOtherFunction(FunctionType otherType) { this.name = otherType.getReferenceName(); this.sourceNode = otherType.getSource(); this.parametersNode = otherType.getParametersNode(); this.returnType = otherType.getReturnType(); this.typeOfThis = otherType.getTypeOfThis(); this.templateTypeMap = otherType.getTemplateTypeMap(); this.isConstructor = otherType.isConstructor(); this.isNativeType = otherType.isNativeObjectType(); return this; }
private void visitFunctionDeclaration(FunctionType ftype, List<String> skipTemplateParams) { visitFunctionParameters(ftype, true, skipTemplateParams); JSType type = ftype.getReturnType(); final JSType typeOfThis = ftype.getTypeOfThis(); if (type == null) return; emit(":"); // Closure conflates 'undefined' and 'void', and in general visitType always emits `undefined` // for that type. // In idiomatic TypeScript, `void` is used for function return types, and the "void", // "undefined" types are not the same. if (type.isVoidType()) { emit("void"); } else if (typeOfThis != null && typeOfThis.isTemplateType() && typeOfThis.equals(type)) { // Special case: prefer polymorphic `this` type to templatized `this` param emit("this"); } else { visitType(type); } }
private static boolean validateThis( Node callOrNew, FunctionType functionType, boolean isCallInvocation) { if (callOrNew.isNew()) { return true; } JSType thisType = functionType.getTypeOfThis(); if (thisType == null || thisType.isUnknownType()) { return true; } Node thisNode = isCallInvocation ? callOrNew.getSecondChild() : callOrNew.getFirstFirstChild(); JSType thisNodeType = thisNode.getJSType().restrictByNotNullOrUndefined(); return thisNodeType.isSubtypeOf(thisType); }
private static boolean validateThis( Node callOrNew, FunctionType functionType, boolean isCallInvocation) { if (callOrNew.isNew()) { return true; } JSType thisType = functionType.getTypeOfThis(); if (thisType.isUnknownType()) { return true; } Node thisNode = isCallInvocation ? callOrNew.getSecondChild() : callOrNew.getFirstFirstChild(); JSType thisNodeType = thisNode.getJSType().restrictByNotNullOrUndefined(); return thisNodeType.isSubtype(thisType); }
/** Copies all the information from another function type. */ public FunctionBuilder copyFromOtherFunction(FunctionType otherType) { int isNative = otherType.isNativeObjectType() ? IS_NATIVE : 0; int isAbstract = otherType.isAbstract() ? IS_ABSTRACT : 0; int inferredReturnType = otherType.isReturnTypeInferred() ? INFERRED_RETURN_TYPE : 0; this.name = otherType.getReferenceName(); this.sourceNode = otherType.getSource(); this.parametersNode = otherType.getParametersNode(); this.returnType = otherType.getReturnType(); this.typeOfThis = otherType.getTypeOfThis(); this.templateTypeMap = otherType.getTemplateTypeMap(); this.kind = otherType.getKind(); this.properties = isNative | isAbstract | inferredReturnType; return this; }
private Map<TemplateType, JSType> inferTemplateTypesFromParameters( FunctionType fnType, Node call) { if (fnType.getTemplateTypeMap().getTemplateKeys().isEmpty()) { return Collections.emptyMap(); } Map<TemplateType, JSType> resolvedTypes = Maps.newIdentityHashMap(); Set<JSType> seenTypes = Sets.newIdentityHashSet(); Node callTarget = call.getFirstChild(); if (NodeUtil.isGet(callTarget)) { Node obj = callTarget.getFirstChild(); maybeResolveTemplatedType( fnType.getTypeOfThis(), getJSType(obj), resolvedTypes, seenTypes); } if (call.hasMoreThanOneChild()) { maybeResolveTemplateTypeFromNodes( fnType.getParameters(), call.getSecondChild().siblings(), resolvedTypes, seenTypes); } return resolvedTypes; }
@Override public JSType getPropertyType(String name) { if (!hasOwnProperty(name)) { // Define the "call", "apply", and "bind" functions lazily. boolean isCall = "call".equals(name); boolean isBind = "bind".equals(name); if (isCall || isBind) { defineDeclaredProperty(name, getCallOrBindSignature(isCall), source); } else if ("apply".equals(name)) { // Define the "apply" function lazily. FunctionParamBuilder builder = new FunctionParamBuilder(registry); // ECMA-262 says that apply's second argument must be an Array // or an arguments object. We don't model the arguments object, // so let's just be forgiving for now. // TODO(nicksantos): Model the Arguments object. builder.addOptionalParams( registry.createNullableType(getTypeOfThis()), registry.createNullableType( registry.getNativeType(JSTypeNative.OBJECT_TYPE))); defineDeclaredProperty(name, new FunctionBuilder(registry) .withParamsNode(builder.build()) .withReturnType(getReturnType()) .withTemplateKeys(getTemplateTypeMap().getTemplateKeys()) .build(), source); } } return super.getPropertyType(name); }
/** * Gets the type of {@code this} in the current scope. */ @Override public JSType getTypeOfThis() { Node root = getRootNode(); if (isGlobal()) { return ObjectType.cast(root.getJSType()); } else if (NodeUtil.isVanillaFunction(root)) { JSType nodeType = root.getJSType(); if (nodeType != null && nodeType.isFunctionType()) { return nodeType.toMaybeFunctionType().getTypeOfThis(); } else { // Executed when the current scope has not been typechecked. return null; } } else { return getParent().getTypeOfThis(); } }