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

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

/*
 * 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 com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import javax.annotation.Nullable;

/**
 * Represents a single target inside a destructuring pattern, whether another pattern or a lhs
 * expression.
 *
 * 

Call {@code inferType} to do type inference on the target lazily. This class is designed so * that a caller can get information about a target, use that information to do additional type * inference, and finally call {@code inferType} if desired. */ public final class DestructuredTarget { private final JSTypeRegistry registry; /** * Holds the STRING_KEY or COMPUTED_PROPERTY for a target in an object pattern. Null for targets * in array patterns. */ @Nullable private final Node objectPatternKey; /** * The target being assigned to. Can be a destructuring pattern or name, if from a declaration, or * an arbitrary lhs expression in an assign. */ @Nullable private final Node node; /** * A supplier to get the type of the pattern containing this target. e.g. for `a` in `const {a} = * {a: 3}`, the supplier provides the record type `{a: number}` * *

This is only called by {@inferType}, not when calling {@code createTarget}. */ private final Supplier patternTypeSupplier; /** The default value of this target, or null if none. e.g. for `[a = 3] = rhs`, this is `3`. */ @Nullable private final Node defaultValue; /** Whether this is a rest key */ private final boolean isRest; /** The destructuring pattern containing this target */ private final Node pattern; private DestructuredTarget( Node pattern, Node node, @Nullable Node defaultValue, @Nullable Node objectPatternKey, JSTypeRegistry registry, Supplier patternTypeSupplier, boolean isRest) { this.pattern = pattern; this.node = node; this.objectPatternKey = objectPatternKey; this.registry = registry; this.patternTypeSupplier = patternTypeSupplier; this.isRest = isRest; this.defaultValue = defaultValue; } /** Returns a COMPUTED_PROP node or null */ @Nullable public Node getComputedProperty() { return hasComputedProperty() ? objectPatternKey : null; } public boolean hasComputedProperty() { return objectPatternKey != null && objectPatternKey.isComputedProp(); } public boolean hasStringKey() { return objectPatternKey != null && objectPatternKey.isStringKey(); } /** Returns a STRING_KEY node or null */ @Nullable public Node getStringKey() { return hasStringKey() ? objectPatternKey : null; } @Nullable public Node getDefaultValue() { return defaultValue; } public boolean hasDefaultValue() { return defaultValue != null; } public Node getNode() { return node; } private static class Builder { private final JSTypeRegistry registry; private final Supplier patternTypeSupplier; private final Node pattern; private Node node; @Nullable private Node defaultValue = null; @Nullable private Node objectPatternKey = null; private boolean isRest = false; Builder(JSTypeRegistry registry, Node pattern, Supplier patternTypeSupplier) { this.registry = registry; this.patternTypeSupplier = patternTypeSupplier; this.pattern = pattern; } Builder setNode(Node node) { this.node = node; return this; } Builder setDefaultValue(Node defaultValue) { this.defaultValue = defaultValue; return this; } Builder setObjectPatternKey(Node objectPatternKey) { this.objectPatternKey = objectPatternKey; return this; } Builder setIsRest(boolean isRest) { this.isRest = isRest; return this; } DestructuredTarget build() { checkNotNull(this.node, "Must set a node"); return new DestructuredTarget( pattern, node, defaultValue, objectPatternKey, registry, patternTypeSupplier, isRest); } } static DestructuredTarget createTarget( JSTypeRegistry registry, JSType destructuringPatternType, Node destructuringChild) { return createTarget(registry, () -> destructuringPatternType, destructuringChild); } /** * Converts a given child of a destructuring pattern (in the AST) to an instance of this class. * * NOTE: does not accept EMPTY nodes */ static DestructuredTarget createTarget( JSTypeRegistry registry, Supplier destructuringPatternType, Node destructuringChild) { checkArgument(destructuringChild.getParent().isDestructuringPattern(), destructuringChild); Builder builder = new Builder(registry, destructuringChild.getParent(), destructuringPatternType); switch (destructuringChild.getToken()) { case STRING_KEY: // const {objectLiteralKey: x} = ... builder.setObjectPatternKey(destructuringChild); Node value = destructuringChild.getFirstChild(); if (value.isDefaultValue()) { builder.setNode(value.getFirstChild()); builder.setDefaultValue(value.getSecondChild()); } else { builder.setNode(value); } break; case COMPUTED_PROP: // const {['objectLiteralKey']: x} = ... builder.setObjectPatternKey(destructuringChild); value = destructuringChild.getSecondChild(); if (value.isDefaultValue()) { builder.setNode(value.getFirstChild()); builder.setDefaultValue(value.getSecondChild()); } else { builder.setNode(value); } break; case OBJECT_PATTERN: // const [{x}] = ... case ARRAY_PATTERN: // const [[x]] = ... case NAME: // const [x] = ... case GETELEM: // [obj[3]] = ... case GETPROP: // [this.x] = ... builder.setNode(destructuringChild); break; case DEFAULT_VALUE: // const [x = 3] = ... builder.setNode(destructuringChild.getFirstChild()); builder.setDefaultValue(destructuringChild.getSecondChild()); break; case ITER_REST: case OBJECT_REST: // const [...x] = ... // const {...x} = ... builder.setNode(destructuringChild.getFirstChild()); builder.setIsRest(true); break; default: throw new IllegalArgumentException( "Unexpected child of destructuring pattern " + destructuringChild); } return builder.build(); } /** * Returns all the targets directly in the given pattern, except for EMPTY nodes * *

EMPTY nodes occur in array patterns with elisions, e.g. `[, , a] = []` */ public static ImmutableList createAllNonEmptyTargetsInPattern( JSTypeRegistry registry, JSType patternType, Node pattern) { return createAllNonEmptyTargetsInPattern(registry, () -> patternType, pattern); } /** * Returns all the targets directly in the given pattern, except for EMPTY nodes * *

EMPTY nodes occur in array patterns with elisions, e.g. `[, , a] = []` */ public static ImmutableList createAllNonEmptyTargetsInPattern( JSTypeRegistry registry, Supplier patternType, Node pattern) { checkArgument(pattern.isDestructuringPattern(), pattern); ImmutableList.Builder builder = ImmutableList.builder(); for (Node child : pattern.children()) { if (child.isEmpty()) { continue; } builder.add(createTarget(registry, patternType, child)); } return builder.build(); } /** * Infers the type of this target before the default value (if any) is applied. * *

e.g. given `a` in const {a = 3} = {}; returns `undefined` */ JSType inferTypeWithoutUsingDefaultValue() { if (pattern.isObjectPattern()) { return inferObjectPatternKeyType(); } else { return inferArrayPatternTargetType(); } } /** * Infers the type of this target * *

e.g. given `a` in const {a = 3} = {}; returns `number` */ JSType inferType() { JSType inferredType = inferTypeWithoutUsingDefaultValue(); if (!inferredType.isUnknownType() && hasDefaultValue()) { JSType defaultValueType = getDefaultValue().getJSType(); if (defaultValueType == null) { defaultValueType = registry.getNativeType(JSTypeNative.UNKNOWN_TYPE); } // We effectively replace '|undefined" with '|typeOfDefaultValue' return registry.createUnionType(inferredType.restrictByNotUndefined(), defaultValueType); } else { return inferredType; } } private JSType inferObjectPatternKeyType() { JSType patternType = patternTypeSupplier.get(); if (isRest) { // TODO(b/128355893): Do smarter inferrence. There are a lot of potential issues with // inference on object-rest, so for now we just give up and say `Object`. return registry.getNativeType(JSTypeNative.OBJECT_TYPE); } if (patternType == null || patternType.isUnknownType()) { return registry.getNativeType(JSTypeNative.UNKNOWN_TYPE); } switch (objectPatternKey.getToken()) { case STRING_KEY: JSType propertyType = patternType.findPropertyType(objectPatternKey.getString()); return propertyType != null ? propertyType : registry.getNativeType(JSTypeNative.UNKNOWN_TYPE); case COMPUTED_PROP: return patternType != null ? patternType .getTemplateTypeMap() .getResolvedTemplateType(registry.getObjectElementKey()) : registry.getNativeType(JSTypeNative.UNKNOWN_TYPE); default: throw new IllegalStateException("Unexpected key " + objectPatternKey); } } private JSType inferArrayPatternTargetType() { JSType patternType = patternTypeSupplier.get(); // e.g. get `number` from `!Iterable` JSType templateTypeOfIterable = patternType.getTemplateTypeMap().getResolvedTemplateType(registry.getIterableTemplate()); if (isRest) { // return `!Array` return registry.createTemplatizedType( registry.getNativeObjectType(JSTypeNative.ARRAY_TYPE), templateTypeOfIterable); } else { return templateTypeOfIterable; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy