All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.AstFactory Maven / Gradle / Ivy

There is a newer version: 9.0.8
Show newest version
/*
 * Copyright 2018 The Closure Compiler Authors.
 *
 * Licensed 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 com.google.javascript.jscomp;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.base.Splitter;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.javascript.jscomp.colors.Color;
import com.google.javascript.jscomp.colors.ColorId;
import com.google.javascript.jscomp.colors.ColorRegistry;
import com.google.javascript.jscomp.colors.StandardColors;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.StaticRef;
import com.google.javascript.rhino.StaticScope;
import com.google.javascript.rhino.StaticSlot;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.ObjectType;
import com.google.javascript.rhino.jstype.TemplateTypeMap;
import com.google.javascript.rhino.jstype.TemplateTypeReplacer;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
import javax.annotation.Nullable;

/**
 * Creates AST nodes and subtrees.
 *
 * 

This class supports creating nodes either with or without type information. * *

The idea is that client code can create the trees of nodes it needs without having to contain * logic for deciding whether type information should be added or not, and only minimal logic for * determining which types to add when they are necessary. Most methods in this class are able to * determine the correct type information from already existing AST nodes and the current scope. * *

AstFactory supports both Closure types (see {@link JSType}) and optimization-only types (see * {@link Color})s. Colors contain less information than JSTypes which puts some restrictions on the * amount of inference this class can do. For example, there's no way to ask "What is the color of * property 'x' on receiver color 'obj'". This is why many methods accept a StaticScope instead of a * Scope: you may pass in a {@link GlobalNamespace} or similar object which contains fully qualified * names, to look up colors for an entire property chain. * *

TODO(b/193800507): delete the methods in this class that only work for JSTypes but not colors. */ final class AstFactory { private static final Splitter DOT_SPLITTER = Splitter.on("."); @Nullable private final ColorRegistry colorRegistry; @Nullable private final JSTypeRegistry registry; // We need the unknown type so frequently, it's worth caching it. private final JSType unknownType; private static final Supplier bigintNumberStringColor = Suppliers.memoize( () -> Color.createUnion( ImmutableSet.of( StandardColors.BIGINT, StandardColors.STRING, StandardColors.NUMBER))); enum TypeMode { JSTYPE, COLOR, NONE } private final TypeMode typeMode; private AstFactory(JSTypeRegistry registry) { this.registry = registry; this.colorRegistry = null; this.unknownType = getNativeType(JSTypeNative.UNKNOWN_TYPE); this.typeMode = TypeMode.JSTYPE; } private AstFactory() { this.registry = null; this.colorRegistry = null; this.unknownType = null; this.typeMode = TypeMode.NONE; } private AstFactory(ColorRegistry colorRegistry) { this.registry = null; this.colorRegistry = colorRegistry; this.unknownType = null; this.typeMode = TypeMode.COLOR; } static AstFactory createFactoryWithoutTypes() { return new AstFactory(); } static AstFactory createFactoryWithTypes(JSTypeRegistry registry) { return new AstFactory(registry); } static AstFactory createFactoryWithColors(ColorRegistry colorRegistry) { return new AstFactory(colorRegistry); } /** Does this class instance add types to the nodes it creates? */ boolean isAddingTypes() { return TypeMode.JSTYPE.equals(this.typeMode); } /** Does this class instance add optimization colors to the nodes it creates? */ boolean isAddingColors() { return TypeMode.COLOR.equals(this.typeMode); } private void assertNotAddingColors() { checkState(!this.isAddingColors(), "method not supported for colors"); } /** * Returns a new EXPR_RESULT node. * *

Statements have no type information, so this is functionally the same as calling {@code * IR.exprResult(expr)}. It exists so that a pass can be consistent about always using {@code * AstFactory} to create new nodes. */ Node exprResult(Node expr) { return IR.exprResult(expr).srcref(expr); } /** * Returns a new EMPTY node. * *

EMPTY Nodes have no type information, so this is functionally the same as calling {@code * IR.empty()}. It exists so that a pass can be consistent about always using {@code AstFactory} * to create new nodes. */ Node createEmpty() { return IR.empty(); } /** * Returns a new BLOCK node. * *

Blocks have no type information, so this is functionally the same as calling {@code * IR.block(statements)}. It exists so that a pass can be consistent about always using {@code * AstFactory} to create new nodes. */ Node createBlock(Node... statements) { return IR.block(statements); } /** * Returns a new IF node. * *

Blocks have no type information, so this is functionally the same as calling {@code * IR.ifNode(cond, then)}. It exists so that a pass can be consistent about always using {@code * AstFactory} to create new nodes. */ Node createIf(Node cond, Node then) { return IR.ifNode(cond, then); } /** * Returns a new IF node. * *

Blocks have no type information, so this is functionally the same as calling {@code * IR.ifNode(cond, then, elseNode)}. It exists so that a pass can be consistent about always using * {@code AstFactory} to create new nodes. */ Node createIf(Node cond, Node then, Node elseNode) { return IR.ifNode(cond, then, elseNode); } /** * Returns a new FOR node. * *

Blocks have no type information, so this is functionally the same as calling {@code * IR.forNode(init, cond, incr, body)}. It exists so that a pass can be consistent about always * using {@code AstFactory} to create new nodes. */ Node createFor(Node init, Node cond, Node incr, Node body) { return IR.forNode(init, cond, incr, body); } /** * Returns a new BREAK node. * *

Breaks have no type information, so this is functionally the same as calling {@code * IR.breakNode()}. It exists so that a pass can be consistent about always using {@code * AstFactory} to create new nodes. */ Node createBreak() { return IR.breakNode(); } /** * Returns a new {@code return} statement. * *

Return statements have no type information, so this is functionally the same as calling * {@code IR.return(value)}. It exists so that a pass can be consistent about always using {@code * AstFactory} to create new nodes. */ Node createReturn(Node value) { return IR.returnNode(value); } /** * Returns a new {@code yield} expression. * * @param type Type we expect to get back after the yield * @param value value to yield */ Node createYield(Type type, Node value) { Node result = IR.yield(value); this.setJSTypeOrColor(type, result); return result; } /** * Returns a new {@code await} expression. * * @param type Type we expect to get back after the await * @param value value to await */ Node createAwait(Type type, Node value) { Node result = IR.await(value); setJSTypeOrColor(type, result); return result; } Node createString(String value) { Node result = IR.string(value); setJSTypeOrColor(type(JSTypeNative.STRING_TYPE, StandardColors.STRING), result); return result; } Node createNumber(double value) { Node result = IR.number(value); setJSTypeOrColor(type(JSTypeNative.NUMBER_TYPE, StandardColors.NUMBER), result); return result; } Node createBoolean(boolean value) { Node result = value ? IR.trueNode() : IR.falseNode(); setJSTypeOrColor(type(JSTypeNative.BOOLEAN_TYPE, StandardColors.BOOLEAN), result); return result; } Node createNull() { Node result = IR.nullNode(); setJSTypeOrColor(type(JSTypeNative.NULL_TYPE, StandardColors.NULL_OR_VOID), result); return result; } Node createVoid(Node child) { Node result = IR.voidNode(child); setJSTypeOrColor(type(JSTypeNative.VOID_TYPE, StandardColors.NULL_OR_VOID), result); return result; } /** Returns a new Node representing the undefined value. */ public Node createUndefinedValue() { // We prefer `void 0` as being shorter than `undefined`. // Also, it's technically possible for malicious code to assign a value to `undefined`. return createVoid(createNumber(0)); } Node createNot(Node child) { Node result = IR.not(child); setJSTypeOrColor(type(JSTypeNative.BOOLEAN_TYPE, StandardColors.BOOLEAN), result); return result; } Node createThis(Type thisType) { Node result = IR.thisNode(); setJSTypeOrColor(thisType, result); return result; } Node createSuper(Type superType) { Node result = IR.superNode(); setJSTypeOrColor(superType, result); return result; } /** * Creates a THIS node with the correct type for the given ES6 class node. * *

With the optimization colors type system, we can support inferring the type of this for * constructors but not generic functions annotated @this */ Node createThisForEs6Class(Node functionNode) { checkState(functionNode.isClass(), functionNode); final Node result = IR.thisNode(); setJSTypeOrColor(getTypeOfThisForEs6Class(functionNode), result); return result; } /** * Creates a THIS node with the correct type for the given ES6 class node. * *

With the optimization colors type system, we can support inferring the type of this for * constructors but not generic functions annotated @this */ Node createThisForEs6ClassMember(Node functionNode) { checkState( functionNode.isMemberFunctionDef() && functionNode.getParent().isClassMembers(), functionNode); Node classNode = functionNode.getGrandparent(); if (functionNode.isStaticMember()) { final Node result = IR.thisNode(); setJSTypeOrColor(type(classNode), result); return result; } else { return createThisForEs6Class(classNode); } } @Nullable private JSType getTypeOfThisForFunctionNode(Node functionNode) { assertNotAddingColors(); if (isAddingTypes()) { FunctionType functionType = getFunctionType(functionNode); return checkNotNull(functionType.getTypeOfThis(), functionType); } else { return null; // not adding type information } } @Nullable private Type getTypeOfThisForEs6Class(Node functionNode) { checkArgument(functionNode.isClass(), functionNode); switch (this.typeMode) { case JSTYPE: return type(getTypeOfThisForFunctionNode(functionNode)); case COLOR: return type(getInstanceOfColor(functionNode.getColor())); case NONE: return noTypeInformation(); } throw new AssertionError(); } private FunctionType getFunctionType(Node functionNode) { checkState( functionNode.isFunction() || functionNode.isClass(), "not a function or class: %s", functionNode); assertNotAddingColors(); // If the function declaration was cast to a different type, we want the original type // from before the cast. final JSType typeBeforeCast = functionNode.getJSTypeBeforeCast(); final FunctionType functionType; if (typeBeforeCast != null) { functionType = typeBeforeCast.assertFunctionType(); } else { functionType = functionNode.getJSTypeRequired().assertFunctionType(); } return functionType; } /** * Creates a NAME node having the type of "this" appropriate for the given ES6 class node * *

With the optimization colors type system, we can support inferring the type of this for * classes but not generic functions annotated @this. */ Node createThisAliasReferenceForEs6Class(String aliasName, Node functionNode) { final Node result = IR.name(aliasName); setJSTypeOrColor(getTypeOfThisForEs6Class(functionNode), result); return result; } /** * Creates a new `let` declaration for a single variable name with a void type and no JSDoc. * *

e.g. `let variableName` */ Node createSingleLetNameDeclaration(String variableName) { return IR.let( createName(variableName, type(JSTypeNative.VOID_TYPE, StandardColors.NULL_OR_VOID))); } /** * Creates a new `let` declaration statement for a single variable name. * *

Takes the type for the variable name from the value node. * *

e.g. `let variableName = value;` */ Node createSingleLetNameDeclaration(String variableName, Node value) { return IR.let(createName(variableName, type(value)), value); } /** * Creates a new `var` declaration statement for a single variable name with void type and no * JSDoc. * *

e.g. `var variableName` */ Node createSingleVarNameDeclaration(String variableName) { return IR.var( createName(variableName, type(JSTypeNative.VOID_TYPE, StandardColors.NULL_OR_VOID))); } /** * Creates a new `var` declaration statement for a single variable name. * *

Takes the type for the variable name from the value node. * *

e.g. `var variableName = value;` */ Node createSingleVarNameDeclaration(String variableName, Node value) { return IR.var(createName(variableName, type(value)), value); } /** * Creates a new `const` declaration statement for a single variable name. * *

Takes the type for the variable name from the value node. * *

e.g. `const variableName = value;` */ Node createSingleConstNameDeclaration(String variableName, Node value) { return IR.constNode(createName(variableName, type(value.getJSType(), value.getColor())), value); } /** * Creates a reference to "arguments" with the type specified in externs, or unknown if the * externs for it weren't included. */ Node createArgumentsReference() { Node result = IR.name("arguments"); switch (this.typeMode) { case JSTYPE: result.setJSType(registry.getNativeType(JSTypeNative.ARGUMENTS_TYPE)); break; case COLOR: result.setColor(colorRegistry.get(StandardColors.ARGUMENTS_ID)); break; case NONE: break; } return result; } /** * Creates a statement declaring a const alias for "arguments". * *

e.g. `const argsAlias = arguments;` */ Node createArgumentsAliasDeclaration(String aliasName) { return createSingleConstNameDeclaration(aliasName, createArgumentsReference()); } Node createName(String name, Type type) { Node result = IR.name(name); setJSTypeOrColor(type, result); return result; } Node createName(StaticScope scope, String name) { Node result = IR.name(name); switch (this.typeMode) { case JSTYPE: JSType definitionType = getVarDefinitionNode(scope, name).getJSType(); // TODO(b/149843534): crash instead of defaulting to unknown result.setJSType(definitionType != null ? definitionType : unknownType); break; case COLOR: Color definitionColor = getVarDefinitionNode(scope, name).getColor(); // TODO(b/149843534): crash instead of defaulting to unknown result.setColor(definitionColor != null ? definitionColor : StandardColors.UNKNOWN); break; case NONE: break; } return result; } Node createNameWithUnknownType(String name) { return createName(name, type(unknownType, StandardColors.UNKNOWN)); } /** * Creates a qualfied name in the given scope. * *

Only works if {@link StaticScope#getSlot(String)} returns a name. In practice, that means * this throws an exception for instance methods and properties, for example. */ Node createQName(StaticScope scope, String qname) { return createQName(scope, DOT_SPLITTER.split(qname)); } /** * Looks up the type of a name from a {@link TypedScope} created from typechecking * * @param globalTypedScope Must be the top, global scope. * @deprecated Prefer {@link #createQName(StaticScope, String)} */ @Deprecated Node createQNameFromTypedScope(TypedScope globalTypedScope, String qname) { checkArgument(globalTypedScope == null || globalTypedScope.isGlobal(), globalTypedScope); assertNotAddingColors(); List nameParts = DOT_SPLITTER.splitToList(qname); checkState(!nameParts.isEmpty()); String receiverPart = nameParts.get(0); Node receiver = IR.name(receiverPart); if (this.isAddingTypes()) { TypedVar var = checkNotNull(globalTypedScope.getVar(receiverPart), receiverPart); receiver.setJSType(checkNotNull(var.getType(), var)); } List otherParts = nameParts.subList(1, nameParts.size()); return this.createGetPropsWithoutColors(receiver, otherParts); } /** * Creates a qualfied name in the given scope. * *

Only works if {@link StaticScope#getSlot(String)} returns a name. In practice, that means * this does not work for instance methods or properties, for example. */ Node createQName(StaticScope scope, Iterable names) { String baseName = checkNotNull(Iterables.getFirst(names, null)); Iterable propertyNames = Iterables.skip(names, 1); return createQName(scope, baseName, propertyNames); } /** * Creates a qualfied name in the given scope. * *

Only works if {@link StaticScope#getSlot(String)} returns a name. In practice, that means * this does not work for instance methods or properties, for example. */ Node createQName(StaticScope scope, String baseName, String... propertyNames) { checkNotNull(baseName); return createQName(scope, baseName, Arrays.asList(propertyNames)); } /** * Creates a qualfied name in the given scope. * *

Only works if {@link StaticScope#getSlot(String)} returns a name. In practice, that means * this does not work for instance methods or properties, for example. */ Node createQName(StaticScope scope, String baseName, Iterable propertyNames) { Node baseNameNode = createName(scope, baseName); Node qname = baseNameNode; String name = baseName; for (String propertyName : propertyNames) { name += "." + propertyName; Type type = null; if (isAddingTypes() || isAddingColors()) { Node def = checkNotNull(scope.getSlot(name), "Cannot find name %s in StaticScope.", name) .getDeclaration() .getNode(); type = type(def); } qname = createGetProp(qname, propertyName, type); } return qname; } Node createQNameWithUnknownType(String qname) { return createQNameWithUnknownType(DOT_SPLITTER.split(qname)); } private Node createQNameWithUnknownType(Iterable names) { String baseName = checkNotNull(Iterables.getFirst(names, null)); Iterable propertyNames = Iterables.skip(names, 1); return createQNameWithUnknownType(baseName, propertyNames); } Node createQNameWithUnknownType(String baseName, Iterable propertyNames) { Node baseNameNode = createNameWithUnknownType(baseName); return createGetPropsWithUnknownType(baseNameNode, propertyNames); } /** * Creates a Node representing .prototype * *

For example, given the AST for `Foo`, returns `Foo.prototype` */ Node createPrototypeAccess(Node receiver) { Node result = IR.getprop(receiver, "prototype"); switch (this.typeMode) { case JSTYPE: result.setJSType(getJsTypeForProperty(receiver, "prototype")); break; case COLOR: checkNotNull(receiver.getColor(), "Missing color on %s", receiver); ImmutableSet possiblePrototypes = receiver.getColor().getPrototypes(); result.setColor( possiblePrototypes.isEmpty() ? StandardColors.UNKNOWN : Color.createUnion(possiblePrototypes)); break; case NONE: break; } return result; } /** * Creates an access of the given {@code qname} on {@code $jscomp.global} * *

For example, given "Object.defineProperties", returns an AST representation of * "$jscomp.global.Object.defineProperties". * *

This may be useful if adding synthetic code to a local scope, which can shadow the global * like Object you're trying to access. The $jscomp global should not be shadowed. */ Node createJSCompDotGlobalAccess(StaticScope scope, String qname) { Node jscompDotGlobal = createQName(scope, "$jscomp.global"); Node result = createQName(scope, qname); // Move the fully qualified qname onto the $jscomp.global getprop Node qnameRoot = NodeUtil.getRootOfQualifiedName(result); qnameRoot.replaceWith(createGetProp(jscompDotGlobal, qnameRoot.getString(), type(qnameRoot))); return result; } /** @deprecated Use {@link #createGetProp(Node, String, Type)} instead. */ @Deprecated Node createGetPropWithoutColor(Node receiver, String propertyName) { assertNotAddingColors(); Node result = IR.getprop(receiver, propertyName); if (isAddingTypes()) { result.setJSType(getJsTypeForProperty(receiver, propertyName)); } return result; } Node createGetProp(Node receiver, String propertyName, Type type) { Node result = IR.getprop(receiver, propertyName); setJSTypeOrColor(type, result); return result; } /** * Creates a tree of nodes representing `receiver.name1.name2.etc`. * * @deprecated use individual {@link #createGetProp(Node, String, Type)} calls or {@link * #createQName(StaticScope, String)} instead. */ @Deprecated Node createGetPropsWithoutColors(Node receiver, Iterable propertyNames) { assertNotAddingColors(); Node result = receiver; for (String propertyName : propertyNames) { result = createGetPropWithoutColor(result, propertyName); } return result; } /** Creates a tree of nodes representing `receiver.name1.name2.etc`. */ Node createGetPropsWithUnknownType(Node receiver, Iterable propertyNames) { Node result = receiver; for (String propertyName : propertyNames) { result = createGetPropWithUnknownType(result, propertyName); } return result; } Node createGetPropWithUnknownType(Node receiver, String propertyName) { Node result = IR.getprop(receiver, propertyName); setJSTypeOrColor(type(unknownType, StandardColors.UNKNOWN), result); return result; } Node createGetElem(Node receiver, Node key) { Node result = IR.getelem(receiver, key); // TODO(bradfordcsmith): When receiver is an Array or an Object, use the template // type here. setJSTypeOrColor(type(unknownType, StandardColors.UNKNOWN), result); return result; } Node createDelProp(Node target) { Node result = IR.delprop(target); setJSTypeOrColor(type(JSTypeNative.BOOLEAN_TYPE, StandardColors.BOOLEAN), result); return result; } Node createStringKey(String key, Node value) { Node result = IR.stringKey(key, value); setJSTypeOrColor(type(value.getJSType(), value.getColor()), result); return result; } Node createComputedProperty(Node key, Node value) { Node result = IR.computedProp(key, value); setJSTypeOrColor(type(value.getJSType(), value.getColor()), result); return result; } /** * Create a getter definition to be inserted into either a class body or object literal. * *

{@code get name() { return value; }} */ Node createGetterDef(String name, Node value) { JSType returnType = value.getJSType(); // Name is stored on the GETTER_DEF node. The function has no name. Node functionNode = createZeroArgFunction(/* name= */ "", IR.block(createReturn(value)), returnType); Node getterNode = Node.newString(Token.GETTER_DEF, name); getterNode.addChildToFront(functionNode); return getterNode; } Node createIn(Node left, Node right) { Node result = IR.in(left, right); setJSTypeOrColor(type(JSTypeNative.BOOLEAN_TYPE, StandardColors.BOOLEAN), result); return result; } Node createComma(Node left, Node right) { Node result = IR.comma(left, right); setJSTypeOrColor(type(right.getJSType(), right.getColor()), result); return result; } Node createCommas(Node first, Node second, Node... rest) { Node result = createComma(first, second); for (Node next : rest) { result = createComma(result, next); } return result; } Node createAnd(Node left, Node right) { Node result = IR.and(left, right); switch (this.typeMode) { case JSTYPE: JSType leftType = checkNotNull(left.getJSType(), left); JSType rightType = checkNotNull(right.getJSType(), right); result.setJSType(this.registry.createUnionType(leftType, rightType)); break; case COLOR: Color leftColor = checkNotNull(left.getColor(), left); Color rightColor = checkNotNull(right.getColor(), right); result.setColor(Color.createUnion(ImmutableSet.of(leftColor, rightColor))); break; case NONE: break; } return result; } Node createOr(Node left, Node right) { Node result = IR.or(left, right); switch (this.typeMode) { case JSTYPE: JSType leftType = checkNotNull(left.getJSType(), left); JSType rightType = checkNotNull(right.getJSType(), right); result.setJSType(this.registry.createUnionType(leftType, rightType)); break; case COLOR: Color leftColor = checkNotNull(left.getColor(), left); Color rightColor = checkNotNull(right.getColor(), right); result.setColor(Color.createUnion(ImmutableSet.of(leftColor, rightColor))); break; case NONE: break; } return result; } Node createAdd(Node left, Node right) { Node result = IR.add(left, right); // Note: this result type could be made tighter if it proves useful for optimizations later on // like setting the string type if both operands are strings. switch (this.typeMode) { case JSTYPE: result.setJSType(getNativeType(JSTypeNative.BIGINT_NUMBER_STRING)); break; case COLOR: result.setColor(bigintNumberStringColor.get()); break; case NONE: break; } return result; } Node createSub(Node left, Node right) { Node result = IR.sub(left, right); setJSTypeOrColor(type(JSTypeNative.NUMBER_TYPE, StandardColors.NUMBER), result); return result; } Node createInc(Node operand, boolean isPost) { Node result = IR.inc(operand, isPost); setJSTypeOrColor(type(JSTypeNative.NUMBER_TYPE, StandardColors.NUMBER), result); return result; } Node createLessThan(Node left, Node right) { Node result = IR.lt(left, right); setJSTypeOrColor(type(JSTypeNative.BOOLEAN_TYPE, StandardColors.BOOLEAN), result); return result; } Node createCall(Node callee, Type resultType, Node... args) { Node result = NodeUtil.newCallNode(callee, args); setJSTypeOrColor(resultType, result); return result; } Node createCallWithUnknownType(Node callee, Node... args) { return createCall(callee, type(unknownType, StandardColors.UNKNOWN), args); } /** * Creates a call to Object.assign that returns the specified type. * *

Object.assign returns !Object in the externs, which can lose type information if the actual * type is known. */ Node createObjectDotAssignCall(StaticScope scope, Type returnType, Node... args) { Node objAssign = createQName(scope, "Object", "assign"); Node result = createCall(objAssign, returnType, args); switch (this.typeMode) { case JSTYPE: // Make a unique function type that returns the exact type we've inferred it to be. // Object.assign in the externs just returns !Object, which loses type information. JSType returnJSType = returnType.getJSType(registry); JSType objAssignType = registry.createFunctionTypeWithVarArgs( returnJSType, registry.getNativeType(JSTypeNative.OBJECT_TYPE), registry.createUnionType(JSTypeNative.OBJECT_TYPE, JSTypeNative.NULL_TYPE)); objAssign.setJSType(objAssignType); break; case COLOR: case NONE: break; } return result; } Node createNewNode(Node target, Node... args) { Node result = IR.newNode(target, args); switch (this.typeMode) { case JSTYPE: JSType instanceType = target.getJSType(); if (instanceType.isFunctionType()) { instanceType = instanceType.toMaybeFunctionType().getInstanceType(); } else { instanceType = getNativeType(JSTypeNative.UNKNOWN_TYPE); } result.setJSType(instanceType); break; case COLOR: result.setColor(getInstanceOfColor(target.getColor())); break; case NONE: break; } return result; } private Color getInstanceOfColor(Color color) { ImmutableSet possibleInstanceColors = color.getInstanceColors(); return possibleInstanceColors.isEmpty() ? StandardColors.UNKNOWN : Color.createUnion(possibleInstanceColors); } /** * Create a call that returns an instance of the given class type. * *

This method is intended for use in special cases, such as calling `super()` in a * constructor. */ Node createConstructorCall(Type classType, Node callee, Node... args) { Node result = NodeUtil.newCallNode(callee, args); switch (this.typeMode) { case JSTYPE: JSType classJSType = classType.getJSType(registry); FunctionType constructorType = checkNotNull(classJSType.toMaybeFunctionType()); ObjectType instanceType = checkNotNull(constructorType.getInstanceType()); result.setJSType(instanceType); break; case COLOR: result.setColor(getInstanceOfColor(classType.getColor(colorRegistry))); break; case NONE: break; } return result; } /** Creates a statement `lhs = rhs;`. */ Node createAssignStatement(Node lhs, Node rhs) { return exprResult(createAssign(lhs, rhs)); } /** Creates an assignment expression `lhs = rhs` */ Node createAssign(Node lhs, Node rhs) { Node result = IR.assign(lhs, rhs); setJSTypeOrColor(type(rhs), result); return result; } /** Creates an assignment expression `lhs = rhs` */ Node createAssign(String lhsName, Node rhs) { Node name = createName(lhsName, type(rhs)); return createAssign(name, rhs); } /** * Creates an object-literal with zero or more elements, `{}`. * *

The type of the literal, if assigned, may be a supertype of the known properties. */ Node createObjectLit(Node... elements) { Node result = IR.objectlit(elements); switch (this.typeMode) { case JSTYPE: result.setJSType(registry.createAnonymousObjectType(null)); break; case COLOR: result.setColor(StandardColors.TOP_OBJECT); break; case NONE: break; } return result; } /** Creates an object-literal with zero or more elements and a specific type. */ Node createObjectLit(Type type, Node... elements) { Node result = IR.objectlit(elements); setJSTypeOrColor(type, result); return result; } public Node createQuotedStringKey(String key, Node value) { Node result = IR.stringKey(key, value); result.setQuotedString(); return result; } /** Creates an empty function `function() {}` */ Node createEmptyFunction(Type type) { Node result = NodeUtil.emptyFunction(); if (isAddingTypes()) { checkArgument(type.getJSType(registry).isFunctionType(), type); } setJSTypeOrColor(type, result); return result; } /** Creates an empty function `function*() {}` */ Node createEmptyGeneratorFunction(Type type) { Node result = createEmptyFunction(type); result.setIsGeneratorFunction(true); return result; } /** * Creates a function `function name(paramList) { body }` * * @param name STRING node - empty string if no name * @param paramList PARAM_LIST node * @param body BLOCK node * @param type type to apply to the function itself */ Node createFunction(String name, Node paramList, Node body, Type type) { Node nameNode = createName(name, type); Node result = IR.function(nameNode, paramList, body); if (isAddingTypes()) { checkArgument(type.getJSType(registry).isFunctionType(), type); } setJSTypeOrColor(type, result); return result; } Node createParamList(String... parameterNames) { final Node paramList = IR.paramList(); for (String parameterName : parameterNames) { paramList.addChildToBack(createNameWithUnknownType(parameterName)); } return paramList; } Node createZeroArgFunction(String name, Node body, @Nullable JSType returnType) { FunctionType functionType = isAddingTypes() ? registry.createFunctionType(returnType).toMaybeFunctionType() : null; return createFunction( name, IR.paramList(), body, type(functionType, StandardColors.TOP_OBJECT)); } Node createZeroArgGeneratorFunction(String name, Node body, @Nullable JSType returnType) { Node result = createZeroArgFunction(name, body, returnType); result.setIsGeneratorFunction(true); return result; } Node createZeroArgArrowFunctionForExpression(Node expression) { Node result = IR.arrowFunction(IR.name(""), IR.paramList(), expression); switch (this.typeMode) { case JSTYPE: // It feels like we should be adding type-of-this here, but it should remain unknown, // because you're allowed to supply any kind of value of `this` when calling an arrow // function. It will just be ignored in favor of the `this` in the scope where the // arrow was defined. FunctionType functionType = FunctionType.builder(registry) .withReturnType(expression.getJSTypeRequired()) .withParameters() .buildAndResolve(); result.setJSType(functionType); break; case COLOR: result.setColor(StandardColors.TOP_OBJECT); break; case NONE: break; } return result; } Node createMemberFunctionDef(String name, Node function) { // A function used for a member function definition must have an empty name, // because the name string goes on the MEMBER_FUNCTION_DEF node. checkArgument(function.getFirstChild().getString().isEmpty(), function); Node result = IR.memberFunctionDef(name, function); setJSTypeOrColor(type(function), result); return result; } Node createSheq(Node expr1, Node expr2) { Node result = IR.sheq(expr1, expr2); setJSTypeOrColor(type(JSTypeNative.BOOLEAN_TYPE, StandardColors.BOOLEAN), result); return result; } Node createEq(Node expr1, Node expr2) { Node result = IR.eq(expr1, expr2); setJSTypeOrColor(type(JSTypeNative.BOOLEAN_TYPE, StandardColors.BOOLEAN), result); return result; } Node createNe(Node expr1, Node expr2) { Node result = IR.ne(expr1, expr2); setJSTypeOrColor(type(JSTypeNative.BOOLEAN_TYPE, StandardColors.BOOLEAN), result); return result; } Node createHook(Node condition, Node expr1, Node expr2) { Node result = IR.hook(condition, expr1, expr2); switch (this.typeMode) { case JSTYPE: result.setJSType(registry.createUnionType(expr1.getJSType(), expr2.getJSType())); break; case COLOR: result.setColor(Color.createUnion(ImmutableSet.of(expr1.getColor(), expr2.getColor()))); break; case NONE: break; } return result; } Node createArraylit(Node... elements) { return createArraylit(Arrays.asList(elements)); } Node createArraylit(Iterable elements) { Node result = IR.arraylit(elements); switch (this.typeMode) { case JSTYPE: result.setJSType( registry.createTemplatizedType( registry.getNativeObjectType(JSTypeNative.ARRAY_TYPE), // TODO(nickreid): Use a reasonable template type. Remeber to consider SPREAD. getNativeType(JSTypeNative.UNKNOWN_TYPE))); break; case COLOR: result.setColor(colorRegistry.get(StandardColors.ARRAY_ID)); break; case NONE: break; } return result; } Node createJSCompMakeIteratorCall(Node iterable, StaticScope scope) { Node makeIteratorName = createQName(scope, "$jscomp.makeIterator"); final Type type; switch (this.typeMode) { case JSTYPE: // Since createCall (currently) doesn't handle templated functions, fill in the template // types // of makeIteratorName manually. // e.g get `number` from `Iterable` JSType iterableType = iterable .getJSType() .getTemplateTypeMap() .getResolvedTemplateType(registry.getIterableTemplate()); JSType makeIteratorType = makeIteratorName.getJSType(); // e.g. replace // function(Iterable): Iterator // with // function(Iterable): Iterator makeIteratorName.setJSType( replaceTemplate(makeIteratorType, ImmutableList.of(iterableType))); type = type(makeIteratorName.getJSType().assertFunctionType().getReturnType()); break; case COLOR: type = type(colorRegistry.get(StandardColors.ITERATOR_ID)); break; case NONE: type = noTypeInformation(); break; default: throw new AssertionError(); } return createCall(makeIteratorName, type, iterable); } Node createJscompArrayFromIteratorCall(Node iterator, StaticScope scope) { Node makeIteratorName = createQName(scope, "$jscomp.arrayFromIterator"); final Type resultType; switch (this.typeMode) { case JSTYPE: // Since createCall (currently) doesn't handle templated functions, fill in the template // types of makeIteratorName manually. JSType iterableType = iterator .getJSType() .getTemplateTypeMap() .getResolvedTemplateType(registry.getIteratorValueTemplate()); JSType makeIteratorType = makeIteratorName.getJSType(); // e.g. replace // function(Iterator): Array // with // function(Iterator): Array makeIteratorName.setJSType( replaceTemplate(makeIteratorType, ImmutableList.of(iterableType))); resultType = type(makeIteratorName.getJSType().assertFunctionType().getReturnType()); break; case COLOR: // colors don't include generics, so just set the return type to Array. resultType = type(colorRegistry.get(StandardColors.ARRAY_ID)); break; case NONE: resultType = noTypeInformation(); break; default: throw new AssertionError(); } return createCall(makeIteratorName, resultType, iterator); } Node createJscompArrayFromIterableCall(Node iterable, StaticScope scope) { // TODO(b/193800507): consider making this verify the arrayFromIterable $jscomp runtime library // is injected. Node makeIterableName = createQName(scope, "$jscomp.arrayFromIterable"); final Type resultType; switch (this.typeMode) { case JSTYPE: // Since createCall (currently) doesn't handle templated functions, fill in the template // types of makeIteratorName manually. JSType iterableType = iterable .getJSType() .getTemplateTypeMap() .getResolvedTemplateType(registry.getIterableTemplate()); JSType makeIterableType = makeIterableName.getJSType(); // e.g. replace // function(Iterable): Array // with // function(Iterable): Array makeIterableName.setJSType( replaceTemplate(makeIterableType, ImmutableList.of(iterableType))); resultType = type(makeIterableName.getJSType().assertFunctionType().getReturnType()); break; case COLOR: // colors don't include generics, so just set the return type to Array. resultType = type(colorRegistry.get(StandardColors.ARRAY_ID)); break; case NONE: resultType = noTypeInformation(); break; default: throw new AssertionError(); } return createCall(makeIterableName, resultType, iterable); } /** * Given an iterable like {@code rhs} in * *

{@code
   * for await (lhs of rhs) { block(); }
   * }
* *

returns a call node for the {@code rhs} wrapped in a {@code $jscomp.makeAsyncIterator} call. * *

{@code
   * $jscomp.makeAsyncIterator(rhs)
   * }
*/ Node createJSCompMakeAsyncIteratorCall(Node iterable, StaticScope scope) { Node makeIteratorAsyncName = createQName(scope, "$jscomp.makeAsyncIterator"); final Type resultType; switch (this.typeMode) { case JSTYPE: // Since createCall (currently) doesn't handle templated functions, fill in the template // types of makeIteratorName manually. // e.g get `number` from `AsyncIterable` JSType asyncIterableType = JsIterables.maybeBoxIterableOrAsyncIterable(iterable.getJSType(), registry) .orElse(unknownType); JSType makeAsyncIteratorType = makeIteratorAsyncName.getJSType(); // e.g. replace // function(AsyncIterable): AsyncIterator // with // function(AsyncIterable): AsyncIterator makeIteratorAsyncName.setJSType( replaceTemplate(makeAsyncIteratorType, ImmutableList.of(asyncIterableType))); resultType = type(makeIteratorAsyncName.getJSType().assertFunctionType().getReturnType()); break; case COLOR: resultType = type(colorRegistry.get(StandardColors.ASYNC_ITERATOR_ITERABLE_ID)); break; case NONE: resultType = noTypeInformation(); break; default: throw new AssertionError(); } return createCall(makeIteratorAsyncName, resultType, iterable); } private JSType replaceTemplate(JSType templatedType, ImmutableList templateTypes) { TemplateTypeMap typeMap = registry .getEmptyTemplateTypeMap() .copyWithExtension(templatedType.getTemplateTypeMap().getTemplateKeys(), templateTypes); TemplateTypeReplacer replacer = TemplateTypeReplacer.forPartialReplacement(registry, typeMap); return templatedType.visit(replacer); } /** * Creates an empty generator function with the correct return type to be an argument to * $jscomp.AsyncGeneratorWrapper. * * @param asyncGeneratorWrapperType the specific type of the $jscomp.AsyncGeneratorWrapper with * its template filled in. Should be the type on the node returned from * createAsyncGeneratorWrapperReference. */ Node createEmptyAsyncGeneratorWrapperArgument(JSType asyncGeneratorWrapperType) { Type generatorType = noTypeInformation(); if (isAddingTypes()) { if (asyncGeneratorWrapperType.isUnknownType()) { // Not injecting libraries? generatorType = type( registry.createFunctionType( replaceTemplate( getNativeType(JSTypeNative.GENERATOR_TYPE), ImmutableList.of(unknownType)))); } else { // Generator<$jscomp.AsyncGeneratorWrapper$ActionRecord> JSType innerFunctionReturnType = Iterables.getOnlyElement( asyncGeneratorWrapperType.toMaybeFunctionType().getParameters()) .getJSType(); generatorType = type(registry.createFunctionType(innerFunctionReturnType)); } } else if (isAddingColors()) { // colors don't model function types, so it's fine to fallback to the top object. generatorType = type(StandardColors.TOP_OBJECT); } return createEmptyGeneratorFunction(generatorType); } Node createJscompAsyncExecutePromiseGeneratorFunctionCall( StaticScope scope, Node generatorFunction) { Node jscompDotAsyncExecutePromiseGeneratorFunction = createQName(scope, "$jscomp.asyncExecutePromiseGeneratorFunction"); Type resultType = type(unknownType); if (isAddingTypes()) { // TODO(bradfordcsmith): Maybe update the type to be more specific // Currently this method expects `function(): !Generator` and returns `Promise`. // Since we propagate type information only if type checking has already run, // these unknowns probably don't matter, but we should be able to be more specific with the // return type at least. resultType = type( jscompDotAsyncExecutePromiseGeneratorFunction .getJSType() .assertFunctionType() .getReturnType()); } else if (isAddingColors()) { resultType = type(colorRegistry.get(StandardColors.PROMISE_ID)); } return createCall(jscompDotAsyncExecutePromiseGeneratorFunction, resultType, generatorFunction); } private JSType getNativeType(JSTypeNative nativeType) { checkNotNull(registry, "registry is null"); return checkNotNull( registry.getNativeType(nativeType), "native type not found: %s", nativeType); } private Node getVarDefinitionNode(StaticScope scope, String name) { StaticSlot var = checkNotNull(scope.getSlot(name), "Missing var %s in scope %s", name, scope); StaticRef declaration = checkNotNull( var.getDeclaration(), "Cannot find type for var with missing declaration %s", var); return checkNotNull(declaration.getNode(), "Missing node for declaration %s", declaration); } private JSType getJsTypeForProperty(Node receiver, String propertyName) { // NOTE: we use both findPropertyType and getPropertyType because they are subtly // different: findPropertyType works on JSType, autoboxing scalars and joining unions, // but it returns null if the type is not found and does not handle dynamic types of // Function.prototype.call and .apply; whereas getPropertyType does not autobox nor // iterate over unions, but it does synthesize the function properties correctly, and // it returns unknown instead of null if the property is missing. JSType getpropType = null; JSType receiverJSType = receiver.getJSType(); if (receiverJSType != null) { getpropType = receiverJSType.findPropertyType(propertyName); if (getpropType == null) { ObjectType receiverObjectType = ObjectType.cast(receiverJSType.autobox()); getpropType = receiverObjectType == null ? unknownType : receiverObjectType.getPropertyType(propertyName); } } if (getpropType == null) { getpropType = unknownType; } // TODO(bradfordcsmith): Special case $jscomp.global until we annotate its type correctly. if (getpropType.isUnknownType() && propertyName.equals("global") && receiver.matchesName("$jscomp")) { getpropType = getNativeType(JSTypeNative.GLOBAL_THIS); } return getpropType; } private void setJSTypeOrColor(Type type, Node result) { switch (this.typeMode) { case JSTYPE: result.setJSType(type.getJSType(registry)); break; case COLOR: result.setColor(type.getColor(colorRegistry)); break; case NONE: break; } } interface Type { JSType getJSType(JSTypeRegistry registry); Color getColor(ColorRegistry registry); } private static final class TypeOnNode implements Type { private final Node n; TypeOnNode(Node n) { this.n = n; } @Override public JSType getJSType(JSTypeRegistry registry) { return checkNotNull(this.n.getJSType(), n); } @Override public Color getColor(ColorRegistry registry) { return checkNotNull(this.n.getColor(), n); } } private static final class JSTypeOrColor implements Type { private final JSType jstype; private final JSTypeNative jstypeNative; private final Color color; private final ColorId colorId; JSTypeOrColor(JSTypeNative jstypeNative, ColorId colorId) { this.jstypeNative = jstypeNative; this.jstype = null; this.color = null; this.colorId = colorId; } JSTypeOrColor(JSTypeNative jstypeNative, Color color) { this.jstypeNative = jstypeNative; this.jstype = null; this.color = color; this.colorId = null; } JSTypeOrColor(JSType jstype, Color color) { this.jstype = jstype; this.jstypeNative = null; this.color = color; this.colorId = null; } @Override public JSType getJSType(JSTypeRegistry registry) { return this.jstype != null ? this.jstype : registry.getNativeType(jstypeNative); } @Override public Color getColor(ColorRegistry registry) { return this.color != null ? this.color : registry.get(checkNotNull(this.colorId)); } } /** Uses the JSType or Color of the given node as a template for adding type information */ static Type type(Node node) { return new TypeOnNode(node); } static Type type(JSType type) { return new JSTypeOrColor(type, null); } static Type type(JSTypeNative type) { return new JSTypeOrColor(type, (Color) null); } static Type type(Color type) { return new JSTypeOrColor((JSType) null, type); } static Type type(ColorId type) { return new JSTypeOrColor(null, type); } static Type type(JSType type, Color color) { return new JSTypeOrColor(type, color); } static Type type(JSTypeNative type, Color color) { return new JSTypeOrColor(type, color); } private static Type noTypeInformation() { return new JSTypeOrColor((JSType) null, null); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy