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

com.oracle.js.parser.ir.Scope Maven / Gradle / Ivy

/*
 * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package com.oracle.js.parser.ir;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;

import org.graalvm.collections.EconomicMap;
import org.graalvm.collections.MapCursor;

import com.oracle.truffle.api.strings.TruffleString;

/**
 * Represents a binding scope (corresponds to LexicalEnvironment or VariableEnvironment).
 */
public final class Scope {
    private final Scope parent;
    private final int type;
    private final int flags;

    private static final int BLOCK_SCOPE = 1 << 0;
    private static final int FUNCTION_BODY_SCOPE = 1 << 1;
    private static final int FUNCTION_PARAMETER_SCOPE = 1 << 2;
    private static final int CATCH_PARAMETER_SCOPE = 1 << 3;
    private static final int GLOBAL_SCOPE = 1 << 4;
    private static final int MODULE_SCOPE = 1 << 5;
    private static final int FUNCTION_TOP_SCOPE = 1 << 6;
    private static final int SWITCH_BLOCK_SCOPE = 1 << 7;
    /** Class head scope = LexicalEnvironment of the class (containing the class name binding). */
    private static final int CLASS_HEAD_SCOPE = 1 << 8;
    /** Class body scope = PrivateEnvironment of the class (containing private names and brands). */
    private static final int CLASS_BODY_SCOPE = 1 << 9;
    private static final int EVAL_SCOPE = 1 << 10;
    private static final int ARROW_FUNCTION_PARAMETER_SCOPE = 1 << 11;

    /** Scope is in a function context. {@code new.target} is available. */
    private static final int IN_FUNCTION = 1 << 16;
    /** Scope is in a method context. Super property accesses are allowed. */
    private static final int IN_METHOD = 1 << 17;
    /** Scope is in a derived class constructor. Super calls are allowed. */
    private static final int IN_DERIVED_CONSTRUCTOR = 1 << 18;
    /** Scope is in a class field initializer. 'arguments' is not allowed. */
    private static final int IS_CLASS_FIELD_INITIALIZER = 1 << 19;

    /** Symbol table - keys must be returned in the order they were put in. */
    protected final EconomicMap symbols;
    /** Use map. */
    protected EconomicMap uses;

    private boolean closed;
    private boolean hasBlockScopedOrRedeclaredSymbols;
    private boolean hasPrivateNames;
    private boolean hasClosures;
    private boolean hasEval;
    private boolean hasNestedEval;

    private Scope(Scope parent, int type, int flags) {
        this.parent = parent;
        this.type = type;
        this.symbols = EconomicMap.create();
        this.flags = flags;
    }

    private Scope(Scope parent, int type) {
        this(parent, type, parent == null ? 0 : parent.flags);
    }

    private static int computeFlags(Scope parent, int functionFlags) {
        if ((functionFlags & FunctionNode.IS_ARROW) != 0) {
            // propagate flags from enclosing function scope.
            return parent == null ? 0 : parent.flags;
        } else {
            int flags = 0;
            flags |= IN_FUNCTION;
            flags |= ((functionFlags & FunctionNode.IS_METHOD) != 0) ? IN_METHOD : 0;
            flags |= ((functionFlags & FunctionNode.IS_DERIVED_CONSTRUCTOR) != 0) ? IN_DERIVED_CONSTRUCTOR : 0;
            flags |= ((functionFlags & FunctionNode.IS_CLASS_FIELD_INITIALIZER) != 0) ? IS_CLASS_FIELD_INITIALIZER : 0;
            return flags;
        }
    }

    public static Scope createGlobal() {
        return new Scope(null, FUNCTION_BODY_SCOPE | GLOBAL_SCOPE | FUNCTION_TOP_SCOPE);
    }

    public static Scope createModule() {
        return new Scope(null, FUNCTION_BODY_SCOPE | MODULE_SCOPE | FUNCTION_TOP_SCOPE);
    }

    public static Scope createFunctionBody(Scope parent, int functionFlags, boolean functionTopScope) {
        assert functionTopScope || (parent.isFunctionParameterScope() && parent.isFunctionTopScope());
        return new Scope(parent, FUNCTION_BODY_SCOPE | (functionTopScope ? FUNCTION_TOP_SCOPE : 0), computeFlags(parent, functionFlags));
    }

    public static Scope createFunctionBody(Scope parent) {
        return createFunctionBody(parent, 0, true);
    }

    public static Scope createBlock(Scope parent) {
        return new Scope(parent, BLOCK_SCOPE);
    }

    public static Scope createCatchParameter(Scope parent) {
        return new Scope(parent, CATCH_PARAMETER_SCOPE);
    }

    public static Scope createFunctionParameter(Scope parent, int functionFlags) {
        return new Scope(parent, FUNCTION_PARAMETER_SCOPE | FUNCTION_TOP_SCOPE |
                        ((functionFlags & FunctionNode.IS_ARROW) != 0 ? ARROW_FUNCTION_PARAMETER_SCOPE : 0),
                        computeFlags(parent, functionFlags));
    }

    public static Scope createSwitchBlock(Scope parent) {
        return new Scope(parent, BLOCK_SCOPE | SWITCH_BLOCK_SCOPE);
    }

    public static Scope createClassHead(Scope parent) {
        return new Scope(parent, BLOCK_SCOPE | CLASS_HEAD_SCOPE);
    }

    public static Scope createClassBody(Scope parent) {
        return new Scope(parent, CLASS_BODY_SCOPE);
    }

    public static Scope createEval(Scope parent, boolean strict) {
        return new Scope(parent, EVAL_SCOPE | (strict ? FUNCTION_BODY_SCOPE | FUNCTION_TOP_SCOPE : 0));
    }

    public Scope getParent() {
        return parent;
    }

    /**
     * Get all the symbols defined in this block, in definition order.
     *
     * @return symbol iterator
     */
    public Iterable getSymbols() {
        return symbols.getValues();
    }

    /**
     * Retrieves an existing symbol defined in the current block.
     *
     * @param name the name of the symbol
     * @return an existing symbol with the specified name defined in the current block, or null if
     *         this block doesn't define a symbol with this name.
     */
    public Symbol getExistingSymbol(final String name) {
        return symbols.get(name);
    }

    /**
     * Test if a symbol with this name is defined in the current block.
     *
     * @param name the name of the symbol
     */
    public boolean hasSymbol(final String name) {
        return symbols.containsKey(name);
    }

    /**
     * Get the number of symbols defined in this block.
     */
    public int getSymbolCount() {
        return symbols.size();
    }

    /**
     * Add symbol to the scope if it does not already exist.
     */
    public Symbol putSymbol(final Symbol symbol) {
        assert !closed : "scope is closed";
        Symbol existing = symbols.putIfAbsent(symbol.getName(), symbol);
        if (existing != null) {
            assert (existing.getFlags() & Symbol.KINDMASK) == (symbol.getFlags() & Symbol.KINDMASK) : symbol;
            return existing;
        }
        if (symbol.isBlockScoped() || symbol.isVarRedeclaredHere()) {
            hasBlockScopedOrRedeclaredSymbols = true;
        }
        if (symbol.isPrivateName()) {
            hasPrivateNames = true;
        }
        return null;
    }

    public boolean hasBlockScopedOrRedeclaredSymbols() {
        return hasBlockScopedOrRedeclaredSymbols;
    }

    public boolean hasPrivateNames() {
        return hasPrivateNames;
    }

    public boolean hasDeclarations() {
        return !symbols.isEmpty();
    }

    /**
     * Returns true if the name is lexically declared in this scope or any of its enclosing scopes
     * within this function.
     *
     * @param varName the declared name
     * @param annexB if true, ignore catch parameters
     * @param includeParameters include parameter scope?
     */
    public boolean isLexicallyDeclaredName(final String varName, final boolean annexB, final boolean includeParameters) {
        for (Scope current = this; current != null; current = current.getParent()) {
            Symbol existingSymbol = current.getExistingSymbol(varName);
            if (existingSymbol != null && existingSymbol.isBlockScoped()) {
                if (existingSymbol.isCatchParameter() && annexB) {
                    // https://tc39.es/ecma262/#sec-variablestatements-in-catch-blocks
                    /*
                     * The Block of a Catch clause may contain var declarations that bind a name
                     * that is also bound by the CatchParameter. At runtime, such bindings are
                     * instantiated in the VariableDeclarationEnvironment. They do not shadow the
                     * same-named bindings introduced by the CatchParameter.
                     */
                    continue;
                }
                return true;
            }
            if (includeParameters ? current.isFunctionTopScope() : current.isFunctionBodyScope()) {
                break;
            }
        }
        return false;
    }

    /**
     * Returns a block scoped symbol in this scope or any of its enclosing scopes within this
     * function.
     *
     * @param varName the symbol name
     */
    public Symbol findBlockScopedSymbolInFunction(String varName) {
        for (Scope current = this; current != null; current = current.getParent()) {
            Symbol existingSymbol = current.getExistingSymbol(varName);
            if (existingSymbol != null) {
                if (existingSymbol.isBlockScoped()) {
                    return existingSymbol;
                } else {
                    // early exit
                    break;
                }
            }
            if (current.isFunctionTopScope()) {
                break;
            }
        }
        return null;
    }

    /**
     * Add a private bound identifier.
     *
     * @return true if the private name was added, false if it was already declared (duplicate name)
     */
    public boolean addPrivateName(TruffleString name, int symbolFlags) {
        assert isClassBodyScope();
        // Register a declared private name.
        if (hasSymbol(name.toJavaStringUncached())) {
            assert getExistingSymbol(name.toJavaStringUncached()).isPrivateName();
            return false;
        } else {
            putSymbol(new Symbol(name, Symbol.IS_CONST | Symbol.IS_PRIVATE_NAME | Symbol.HAS_BEEN_DECLARED | symbolFlags));
            return true;
        }
    }

    public boolean findPrivateName(String name) {
        for (Scope current = this; current != null; current = current.parent) {
            if (current.hasSymbol(name)) {
                return true;
            }
        }
        return false;
    }

    public boolean isBlockScope() {
        return (type & BLOCK_SCOPE) != 0;
    }

    public boolean isFunctionBodyScope() {
        return (type & FUNCTION_BODY_SCOPE) != 0;
    }

    public boolean isFunctionParameterScope() {
        return (type & FUNCTION_PARAMETER_SCOPE) != 0;
    }

    public boolean isCatchParameterScope() {
        return (type & CATCH_PARAMETER_SCOPE) != 0;
    }

    public boolean isGlobalScope() {
        return (type & GLOBAL_SCOPE) != 0;
    }

    public boolean isModuleScope() {
        return (type & MODULE_SCOPE) != 0;
    }

    public boolean isFunctionTopScope() {
        return (type & FUNCTION_TOP_SCOPE) != 0;
    }

    public boolean isSwitchBlockScope() {
        return (type & SWITCH_BLOCK_SCOPE) != 0;
    }

    public boolean isClassBodyScope() {
        return (type & CLASS_BODY_SCOPE) != 0;
    }

    public boolean isClassHeadScope() {
        return (type & CLASS_HEAD_SCOPE) != 0;
    }

    public boolean isEvalScope() {
        return (type & EVAL_SCOPE) != 0;
    }

    public boolean isArrowFunctionParameterScope() {
        return (type & ARROW_FUNCTION_PARAMETER_SCOPE) != 0;
    }

    public boolean inFunction() {
        return (flags & IN_FUNCTION) != 0;
    }

    public boolean inMethod() {
        return (flags & IN_METHOD) != 0;
    }

    public boolean inDerivedConstructor() {
        return (flags & IN_DERIVED_CONSTRUCTOR) != 0;
    }

    public boolean inClassFieldInitializer() {
        return (flags & IS_CLASS_FIELD_INITIALIZER) != 0;
    }

    /**
     * Closes the scope for symbol registration.
     */
    public void close() {
        if (closed) {
            return;
        }
        resolveUses();
        closed = true;
    }

    public boolean isClosed() {
        return closed;
    }

    /**
     * Clears defined symbols and moves any local uses into the parent scope.
     */
    public void kill() {
        assert !closed : "scope is closed";
        assert isKillable() : "must not be killed";
        symbols.clear();
        if (uses != null) {
            if (parent != null && !parent.closed) {
                MapCursor cursor = uses.getEntries();
                while (cursor.advance()) {
                    String usedName = cursor.getKey();
                    UseInfo useInfo = cursor.getValue();
                    assert useInfo.isUnresolved();
                    if (useInfo.use == this) {
                        parent.addLocalUse(usedName);
                    }
                    if (useInfo.hasInnerUse()) {
                        useInfo.use = null;
                        parent.addUsesFromInnerScope(usedName, useInfo);
                    }
                }
            }
            uses = null;
        }
        closed = true;
    }

    /**
     * Records the use of an identifier reference in this scope to be resolved.
     */
    public void addIdentifierReference(String name) {
        addLocalUse(name);
    }

    private UseInfo getUseInfo(String name) {
        if (uses == null) {
            return null;
        }
        return uses.get(name);
    }

    private void putUseInfo(String name, UseInfo useInfo) {
        if (uses == null) {
            uses = EconomicMap.create();
        }
        uses.put(name, useInfo);
    }

    private void removeUseInfo(String name) {
        if (uses == null) {
            return;
        }
        uses.removeKey(name);
    }

    /**
     * Records or resolves a use in this scope.
     */
    private void addLocalUse(String name) {
        assert !closed : "scope is closed";
        UseInfo foundUse = getUseInfo(name);
        Symbol foundSymbol = symbols.get(name);
        if (foundSymbol != null) {
            // declared in this scope, can be resolved immediately.
            if (foundUse == null) {
                foundUse = UseInfo.resolvedLocal(name, this);
                putUseInfo(name, foundUse);
                assert foundUse.use == this;
            } else {
                assert foundUse.use == this || foundUse.use == null;
                foundUse.use = this;
            }
            resolveUse(foundUse, this, foundSymbol);
        } else {
            // as of yet unresolved, could still be resolved in the current scope later
            // or in one of the outer scopes eventually.
            if (foundUse == null) {
                foundUse = UseInfo.unresolved(name);
                putUseInfo(name, foundUse);
                assert foundUse.use == null;
            } else {
                assert foundUse.def == null;
                assert foundUse.use == this || foundUse.use == null;
            }
            foundUse.use = this;
        }
    }

    /**
     * Used to resolve or forward unresolved uses from inner scopes.
     */
    private void addUsesFromInnerScope(String name, UseInfo useInfo) {
        assert !closed : "scope is closed";
        if (useInfo.use == null && useInfo.innerUseScopes == null) {
            return;
        }

        Symbol foundSymbol = symbols.get(name);
        if (foundSymbol != null && !isKillable()) {
            // declared here, so we can resolve it immediately.
            resolveUse(useInfo, this, foundSymbol);
            return;
        }
        // merge unresolved use into the parent scope
        UseInfo foundUse = getUseInfo(name);
        if (foundUse == null) {
            foundUse = UseInfo.unresolved(name);
            putUseInfo(name, foundUse);
        } else {
            assert foundUse.use == this || foundUse.use == null;
        }

        if (useInfo.innerUseScopes != null) {
            for (Scope innerScope : useInfo.innerUseScopes) {
                foundUse.addInnerUse(innerScope);
            }
        }
        if (useInfo.use != null) {
            foundUse.addInnerUse(useInfo.use);
        }
    }

    private boolean isKillable() {
        // Delay resolution while parsing cover arrow parameter list in case it will be revoked.
        return isArrowFunctionParameterScope();
    }

    /**
     * Resolves free variables in this scope and forwards unresolved uses to the parent scope.
     */
    public void resolveUses() {
        if (uses == null || uses.isEmpty()) {
            return;
        }

        boolean hasDeclarations = hasDeclarations();
        MapCursor cursor = uses.getEntries();
        while (cursor.advance()) {
            String usedName = cursor.getKey();
            UseInfo useInfo = cursor.getValue();
            if (useInfo.isUnresolved()) {
                Symbol foundSymbol;
                if (hasDeclarations && (foundSymbol = symbols.get(usedName)) != null) {
                    // resolved in this scope
                    resolveUse(useInfo, this, foundSymbol);
                } else {
                    // unresolved, pass on to parent scope, if possible
                    // no use in this scope, skip this scope and remove the use here.
                    if (parent == null || parent.closed) {
                        // A closed parent scope implies a parsing boundary, e.g. eval().
                        // We cannot make any symbols available that are not already captured.
                        // We could resolve these symbols statically but there is currently
                        // no advantage in doing so early.
                        markUseUnresolvable(useInfo);
                    } else {
                        parent.addUsesFromInnerScope(usedName, useInfo);
                    }
                    if (useInfo.use == null) {
                        cursor.remove();
                    }
                }
            }
        }
        if (uses.isEmpty()) {
            uses = null; // free unused memory
        }

        // Note: In case of nested eval, we would still not have to capture variables that are
        // shadowed in all inner scopes that contain eval.
        // Consider e.g.: `{ let x; { let x; { eval("..."); } } }`
        // Here the outer x would not have to be captured (only the inner x). For simplicity,
        // we currently assume all variables in scopes that contain (nested) eval may be captured.
    }

    /**
     * Resolve use in the local and any inner scopes to the scope defining the symbol.
     */
    private static void resolveUse(UseInfo useInfo, Scope defScope, Symbol foundSymbol) {
        String name = useInfo.name;
        // cannot change a resolved scope
        assert useInfo.def == null || useInfo.def == defScope;
        assert name.equals(foundSymbol.getName());
        assert defScope.hasSymbol(foundSymbol.getName());
        if (useInfo.use != null) {
            markSymbolUsed(foundSymbol, defScope, useInfo.use);
        }
        if (useInfo.innerUseScopes != null) {
            for (Scope inner : useInfo.innerUseScopes) {
                UseInfo innerUse = inner.getUseInfo(name);
                if (innerUse == null) {
                    innerUse = UseInfo.unresolved(name);
                    innerUse.use = inner;
                    inner.putUseInfo(name, innerUse);
                }
                innerUse.def = defScope;
                innerUse.innerUseScopes = null;

                markSymbolUsed(foundSymbol, defScope, inner);
            }
            useInfo.innerUseScopes = null;
        }
        useInfo.def = defScope;
    }

    private static void markSymbolUsed(Symbol foundSymbol, Scope defScope, Scope useScope) {
        foundSymbol.setUsed();
        if (defScope != useScope) {
            boolean inClosure = isInClosure(defScope, useScope);
            if (inClosure) {
                foundSymbol.setClosedOver();
                defScope.hasClosures = true;
            } else {
                foundSymbol.setUsedInInnerScope();
            }
        }
    }

    private static void markUseUnresolvable(UseInfo useInfo) {
        String name = useInfo.name;
        assert useInfo.def == null : name; // must not be resolved

        if (useInfo.innerUseScopes != null) {
            for (Scope inner : useInfo.innerUseScopes) {
                UseInfo innerUse = inner.getUseInfo(name);
                if (innerUse != null) {
                    assert innerUse.def == null : name; // must not be resolved
                    if (innerUse.use != null) {
                        innerUse.use.removeUseInfo(name);
                    }
                }
            }
            useInfo.innerUseScopes = null;
        }
    }

    /**
     * Checks if there is a function boundary between the inner and outer scopes.
     */
    private static boolean isInClosure(Scope outer, Scope inner) {
        assert inner != null && outer != null;
        Scope current = inner;
        while (current != null && current != outer) {
            if (current.isFunctionTopScope()) {
                // scopes cross a function boundary
                return true;
            }
            current = current.getParent();
        }
        return false;
    }

    /**
     * Returns true if the scope has closed over variables.
     */
    public boolean hasClosures() {
        return hasClosures;
    }

    /**
     * Does this scope contain a direct eval.
     */
    public boolean hasEval() {
        return hasEval;
    }

    /**
     * Marks this scope as containing a direct eval.
     */
    public void setHasEval() {
        if (!hasEval) {
            hasEval = true;
            setHasNestedEval();
        }
    }

    /**
     * Does this scope or any nested scopes contain a direct eval.
     */
    public boolean hasNestedEval() {
        return hasNestedEval;
    }

    public void setHasNestedEval() {
        // taint all parent scopes
        for (Scope current = this; current != null; current = current.parent) {
            if (current.hasNestedEval || current.closed) {
                break;
            }
            current.hasNestedEval = true;
        }
    }

    @Override
    public String toString() {
        StringJoiner names = new StringJoiner(",", "(", ")");
        for (Symbol symbol : symbols.getValues()) {
            String name = symbol.getName();
            String mark = "";
            if (symbol.isClosedOver()) {
                mark += "'";
            } else if (symbol.isUsedInInnerScope()) {
                mark += "\"";
            } else if (hasNestedEval) {
                if (!symbol.isUsed()) {
                    mark += "*";
                }
            } else if (!symbol.isUsed()) {
                mark += "-";
            }
            names.add(name + mark);
        }

        String usedNames = "";
        if (uses != null) {
            StringJoiner sj = new StringJoiner(",", ">(", ")").setEmptyValue("");
            for (String use : uses.getKeys()) {
                if (!symbols.containsKey(use)) {
                    sj.add(use);
                }
            }
            usedNames = sj.toString();
        }

        String taint = "";
        if (hasClosures) {
            taint += "'";
        } else if (hasNestedEval) {
            taint += "*";
        }
        return "[" + getScopeKindName() + "Scope" + taint + names + usedNames + (parent == null ? "" : ", " + parent + "") + "]";
    }

    private String getScopeKindName() {
        if (isGlobalScope()) {
            return "Global";
        } else if (isModuleScope()) {
            return "Module";
        } else if (isEvalScope()) {
            return "Eval";
        } else if (isFunctionBodyScope()) {
            return "Var";
        } else if (isFunctionParameterScope()) {
            return "Param";
        } else if (isCatchParameterScope()) {
            return "Catch";
        } else if (isSwitchBlockScope()) {
            return "Switch";
        } else if (isClassHeadScope()) {
            return "Class";
        } else if (isClassBodyScope()) {
            return "Private";
        }
        return "";
    }

    /**
     * Tracks use of symbol in the current and any nested scopes.
     */
    static final class UseInfo {
        /** Used name. */
        final String name;
        /** Resolved scope in which the symbol has been defined. Must be a parent scope. */
        Scope def;
        /** Local scope in which the symbol is used. */
        Scope use;
        /** Inner scopes with unresolved uses of the symbol. */
        List innerUseScopes;

        private UseInfo(String name, Scope use, Scope def) {
            this.name = Objects.requireNonNull(name);
            this.use = use;
            this.def = def;
        }

        static UseInfo resolvedLocal(String name, Scope local) {
            return new UseInfo(name, local, local);
        }

        static UseInfo unresolved(String name) {
            return new UseInfo(name, null, null);
        }

        /**
         * Add an unresolved use of the symbol in an inner scope.
         */
        void addInnerUse(Scope useScope) {
            if (innerUseScopes == null) {
                innerUseScopes = new ArrayList<>();
            }
            // there should not be any duplicate scopes in the list
            assert innerUseScopes.stream().noneMatch(s -> s == useScope) : name;
            innerUseScopes.add(useScope);
        }

        boolean isResolved() {
            return !isUnresolved();
        }

        boolean isUnresolved() {
            return def == null || innerUseScopes != null;
        }

        boolean hasInnerUse() {
            return innerUseScopes != null;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy