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

com.shapesecurity.shift.scope.ScopeAnalyzer Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014 Shape Security, Inc.
 *
 * 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.shapesecurity.shift.scope;

import com.shapesecurity.functional.F2;
import com.shapesecurity.functional.Pair;
import com.shapesecurity.functional.data.*;
import com.shapesecurity.shift.ast.*;
import com.shapesecurity.shift.scope.Declaration.Kind;
import com.shapesecurity.shift.visitor.Director;
import com.shapesecurity.shift.visitor.MonoidalReducer;

import com.shapesecurity.shift.visitor.StrictnessReducer;
import org.jetbrains.annotations.NotNull;

public final class ScopeAnalyzer extends MonoidalReducer {
    private final ImmutableSet sloppySet;

    private ScopeAnalyzer(@NotNull Script script) {
        super(new StateMonoid());
        sloppySet = StrictnessReducer.analyze(script);
    }

    private ScopeAnalyzer(@NotNull Module module) {
        super(new StateMonoid());
        sloppySet = ImmutableSet.emptyUsingIdentity();
    }

    @NotNull
    public static GlobalScope analyze(@NotNull Script script) {
        return (GlobalScope) Director.reduceScript(new ScopeAnalyzer(script), script).children.maybeHead().fromJust();
    }

    @NotNull
    public static GlobalScope analyze(@NotNull Module module) {
        return (GlobalScope) Director.reduceModule(new ScopeAnalyzer(module), module).children.maybeHead().fromJust();
    }

    @NotNull
    private State finishFunction(@NotNull Node fnNode, @NotNull State params, @NotNull State body) {
        boolean isArrowFn = fnNode instanceof ArrowExpression;
        Scope.Type fnType = isArrowFn ? Scope.Type.ArrowFunction : Scope.Type.Function;
        if (params.hasParameterExpressions) {
            params = params.withoutParameterExpressions(); // no need to pass that information on
            return new State(params, body.finish(fnNode, fnType, !isArrowFn, this.sloppySet.contains(fnNode))).finish(fnNode, Scope.Type.Parameters);
        } else {
            return new State(params, body).finish(fnNode, fnType, !isArrowFn, this.sloppySet.contains(fnNode));
        }
    }

    @NotNull
    // TODO you'd think you'd need to do this for labelled function declarations too, but the spec actually doesn't say so...
    private ImmutableList getFunctionDeclarations(@NotNull ImmutableList statements) { // get the names of functions declared in the statement list
        ImmutableList potentiallyVarScopedFunctionDeclarations = ImmutableList.empty();
        for (Statement statement : statements) { // TODO this is not a very clean way of doing this
            if (statement instanceof FunctionDeclaration) {
                FunctionDeclaration f = (FunctionDeclaration) statement;
                potentiallyVarScopedFunctionDeclarations = potentiallyVarScopedFunctionDeclarations.cons(f.name);
            }
        }
        return potentiallyVarScopedFunctionDeclarations;
    }

    @NotNull
    @Override
    public State reduceArrowExpression(@NotNull ArrowExpression node, @NotNull State params, @NotNull State body) {
        return finishFunction(node, params, body);
    }

    @NotNull
    @Override
    public State reduceAssignmentExpression(@NotNull AssignmentExpression node, @NotNull State binding, @NotNull State expression) {
        return super.reduceAssignmentExpression(node, binding.addReferences(Accessibility.Write), expression);
    }

    @NotNull
    @Override
    public State reduceBindingIdentifier(@NotNull BindingIdentifier node) {
        if (node.name.equals("*default*")) {
            return new State();
        }
        return new State(
                HashTable.emptyUsingEquality(),
                HashTable.emptyUsingEquality(),
                HashTable.emptyUsingEquality(),
                HashTable.emptyUsingEquality(),
                ImmutableList.empty(),
                false,
                ImmutableList.of(node),
                HashTable.emptyUsingEquality(),
                false
        );
    }

    @NotNull
    @Override
    public State reduceBindingPropertyIdentifier(@NotNull BindingPropertyIdentifier node, @NotNull State binding, @NotNull Maybe init) {
        State s = super.reduceBindingPropertyIdentifier(node, binding, init);
        if (init.isJust()) {
            return s.withParameterExpressions();
        }
        return s;
    }

    @NotNull
    @Override
    public State reduceBindingWithDefault(@NotNull BindingWithDefault node, @NotNull State binding, @NotNull State init) {
        return super.reduceBindingWithDefault(node, binding, init).withParameterExpressions();
    }

    @NotNull
    @Override
    public State reduceBlock(@NotNull Block node, @NotNull ImmutableList statements) {
        return super.reduceBlock(node, statements).withPotentialVarFunctions(getFunctionDeclarations(node.statements)).finish(node, Scope.Type.Block);
    }

    @NotNull
    @Override
    public State reduceCallExpression(@NotNull CallExpression node, @NotNull State callee, @NotNull ImmutableList arguments) {
        State s = super.reduceCallExpression(node, callee, arguments);
        if (node.callee instanceof IdentifierExpression && ((IdentifierExpression) node.callee).name.equals("eval")) {
            return s.taint();
        }
        return s;
    }

    @NotNull
    @Override
    public State reduceCatchClause(@NotNull CatchClause node, @NotNull State binding, @NotNull State body) {
        return super.reduceCatchClause(node, binding.addDeclarations(Kind.CatchParameter), body).finish(node, Scope.Type.Catch);
    }

    @NotNull
    @Override
    public State reduceClassDeclaration(@NotNull ClassDeclaration node, @NotNull State name, @NotNull Maybe _super, @NotNull ImmutableList elements) {
        State s = super.reduceClassDeclaration(node, name, _super, elements).addDeclarations(Kind.ClassName).finish(node, Scope.Type.ClassName);
        return new State(s, name.addDeclarations(Kind.ClassDeclaration));
    }

    @NotNull
    @Override
    public State reduceClassExpression(@NotNull ClassExpression node, @NotNull Maybe name, @NotNull Maybe _super, @NotNull ImmutableList elements) {
        return super.reduceClassExpression(node, name, _super, elements).addDeclarations(Kind.ClassName).finish(node, Scope.Type.ClassName);
    }

    @NotNull
    @Override
    public State reduceCompoundAssignmentExpression(@NotNull CompoundAssignmentExpression node, @NotNull State binding, @NotNull State expression) {
        return super.reduceCompoundAssignmentExpression(node, binding.addReferences(Accessibility.ReadWrite), expression);
    }

    @NotNull
    @Override
    public State reduceComputedMemberExpression(@NotNull ComputedMemberExpression node, @NotNull State object, @NotNull State expression) {
        return super.reduceComputedMemberExpression(node, object, expression).withParameterExpressions();
    }

    @NotNull
    @Override
    public State reduceForInStatement(@NotNull ForInStatement node, @NotNull State left, @NotNull State right, @NotNull State body) {
        return super.reduceForInStatement(node, left.addReferences(Accessibility.Write), right, body).finish(node, Scope.Type.Block);
    }

    @NotNull
    @Override
    public State reduceForOfStatement(@NotNull ForOfStatement node, @NotNull State left, @NotNull State right, @NotNull State body) {
        return super.reduceForOfStatement(node, left.addReferences(Accessibility.Write), right, body).finish(node, Scope.Type.Block);
    }

    @NotNull
    @Override
    public State reduceForStatement(@NotNull ForStatement node, @NotNull Maybe init, @NotNull Maybe test, @NotNull Maybe update, @NotNull State body) {
        return super.reduceForStatement(node, init.map(State::withoutBindingsForParent), test, update, body).finish(node, Scope.Type.Block);
    }

    @NotNull
    @Override
    public State reduceFormalParameters(@NotNull FormalParameters node, @NotNull ImmutableList items, @NotNull Maybe rest) {
        return items.mapWithIndex((F2) Pair::new)
                .foldLeft((x, y) ->
                        new State(x, ((State) y.right).hasParameterExpressions ? ((State) y.right).finish(node.items.index((Integer) y.left).fromJust(), Scope.Type.ParameterExpression) : ((State) y.right)),
                rest.orJust(new State()))
                .addDeclarations(Kind.Parameter);
    }

    // TODO should defining a function count as writing to its name, for symmetry with initialized variable declaration?
    @NotNull
    @Override
    public State reduceFunctionDeclaration(@NotNull FunctionDeclaration node, @NotNull State name, @NotNull State params, @NotNull State body) {
        return new State(name, finishFunction(node, params, body)).addFunctionDeclaration();
        // todo it is possible that this should sometimes add a write-reference per B.3.3
    }

    // TODO should defining a function count as writing to its name, for symmetry with initialized variable declaration
    @NotNull
    @Override
    public State reduceFunctionExpression(@NotNull FunctionExpression node, @NotNull Maybe name, @NotNull State params, @NotNull State body) {
        State primary = finishFunction(node, params, body);
        if (name.isJust()) {
            return new State(name.fromJust(), primary).addDeclarations(Kind.FunctionExpressionName).finish(node, Scope.Type.FunctionName);
        } else {
            return primary; // per spec, no function name scope is created for unnamed expressions.
        }
    }

    @NotNull
    @Override
    public State reduceGetter(@NotNull Getter node, @NotNull State name, @NotNull State body) {
        return new State(name, body.finish(node, Scope.Type.Function, true, this.sloppySet.contains(node)));
        // variables defined in body are not in scope when evaluating name (which may be computed)
    }

    @NotNull
    @Override
    public State reduceIdentifierExpression(@NotNull IdentifierExpression node) {
        Reference ref = new Reference(node);
        return new State(
                HashTable.>emptyUsingEquality().put(node.name, ImmutableList.of(ref)),
                HashTable.emptyUsingEquality(),
                HashTable.emptyUsingEquality(),
                HashTable.emptyUsingEquality(),
                ImmutableList.empty(),
                false,
                ImmutableList.empty(),
                HashTable.emptyUsingEquality(),
                false
        );
    }

    @NotNull
    @Override
    public State reduceIfStatement(@NotNull IfStatement node, @NotNull State test, @NotNull State consequent, @NotNull Maybe alternate) {
        ImmutableList statements = ImmutableList.of(node.consequent);
        if (node.alternate.isJust()) {
            statements = statements.cons(node.alternate.fromJust());
        }
        return super.reduceIfStatement(node, test, consequent, alternate).withPotentialVarFunctions(getFunctionDeclarations(statements));
    }

    @NotNull
    public State reduceImport(@NotNull Import node, @NotNull Maybe defaultBinding, @NotNull ImmutableList namedImports) {
        return super.reduceImport(node, defaultBinding, namedImports).addDeclarations(Kind.Import);
    }

    @NotNull
    @Override
    public State reduceMethod(@NotNull Method node, @NotNull State name, @NotNull State params, @NotNull State body) {
        return new State(name, finishFunction(node, params, body));
    }

    @NotNull
    @Override
    public State reduceModule(@NotNull Module node, @NotNull ImmutableList directives, @NotNull ImmutableList statements) {
        return super.reduceModule(node, directives, statements).finish(node, Scope.Type.Module);
    }

    @NotNull
    @Override
    public State reduceScript(@NotNull Script node, @NotNull ImmutableList directives, @NotNull ImmutableList statements) {
        return super.reduceScript(node, directives, statements).finish(node, Scope.Type.Script);
    }

    @NotNull
    @Override
    public State reduceSetter(@NotNull Setter node, @NotNull State name, @NotNull State param, @NotNull State body) {
        param = param.hasParameterExpressions ? param.finish(node, Scope.Type.ParameterExpression) : param;
        return new State(name, finishFunction(node, param.addDeclarations(Kind.Parameter), body));
        // TODO have the node associated with the parameter's scope be more precise
    }

    @NotNull
    @Override
    public State reduceSwitchCase(@NotNull SwitchCase node, @NotNull State test, @NotNull ImmutableList consequent) {
        return super.reduceSwitchCase(node, test, consequent).finish(node, Scope.Type.Block).withPotentialVarFunctions(getFunctionDeclarations(node.consequent));
    }

    @NotNull
    @Override
    public State reduceSwitchDefault(@NotNull SwitchDefault node, @NotNull ImmutableList consequent) {
        return super.reduceSwitchDefault(node, consequent).finish(node, Scope.Type.Block).withPotentialVarFunctions(getFunctionDeclarations(node.consequent));
    }

    @NotNull
    @Override
    public State reduceUpdateExpression(@NotNull UpdateExpression node, @NotNull State operand) {
        return operand.addReferences(Accessibility.ReadWrite);
        // no-op if operand is a member expression (which will have no bindingsForParent)
    }

    @NotNull
    @Override
    public State reduceVariableDeclaration(@NotNull VariableDeclaration node, @NotNull ImmutableList declarators) {
        return super.reduceVariableDeclaration(node, declarators).addDeclarations(Kind.fromVariableDeclarationKind(node.kind), true);
        // passes bindingsForParent up, for for-in and for-of to add their write-references
    }

    @NotNull
    @Override
    public State reduceVariableDeclarationStatement(@NotNull VariableDeclarationStatement node, @NotNull State declaration) {
        return declaration.withoutBindingsForParent();
    }

    @NotNull
    @Override
    public State reduceVariableDeclarator(@NotNull VariableDeclarator node, @NotNull State binding, @NotNull Maybe init) {
        State res = super.reduceVariableDeclarator(node, binding, init);
        if (init.isJust()) {
            return res.addReferences(Accessibility.Write, true);
            // passes bindingsForParent up, for variableDeclaration to add the appropriate type of declaration
        } else {
            return res;
        }
    }

    @NotNull
    @Override
    public State reduceWithStatement(@NotNull WithStatement node, @NotNull State object, @NotNull State body) {
        return super.reduceWithStatement(node, object, body.finish(node, Scope.Type.With));
    }


    @SuppressWarnings("ProtectedInnerClass")
    public static final class State {
        public final boolean dynamic;
        public final boolean hasParameterExpressions; // to decide if function parameters are in a different scope than function variables. only meaningful on `params` states and their children. true iff `params` has any default values or computed member accesses among its children.
        @NotNull
        public final HashTable> freeIdentifiers;
        @NotNull
        public final HashTable> functionScopedDeclarations;
        @NotNull
        public final HashTable> blockScopedDeclarations;
        @NotNull
        public final HashTable> functionDeclarations; // function declarations are special: they are lexical in blocks and var at the top level of functions and scripts. In particular, at the top of scripts they go in global scope.
        @NotNull
        public final ImmutableList children;
        @NotNull
        public final ImmutableList bindingsForParent; // either references bubbling up to the AssignmentExpression, ForOfStatement, or ForInStatement which writes to them or declarations bubbling up to the VariableDeclaration, FunctionDeclaration, ClassDeclaration, FormalParameters, Setter, Method, or CatchClause which declares them
        @NotNull
        public final HashTable> potentiallyVarScopedFunctionDeclarations; // for annex B.3.3, which says (essentially) that function declarations are *also* var-scoped if doing so is not an early error (although not at the top level; only within functions).

        /*
         * Fully saturated constructor
         */
        private State(
                @NotNull HashTable> freeIdentifiers,
                @NotNull HashTable> functionScopedDeclarations,
                @NotNull HashTable> blockScopedDeclarations,
                @NotNull HashTable> functionDeclarations,
                @NotNull ImmutableList children,
                boolean dynamic,
                @NotNull ImmutableList bindingsForParent,
                @NotNull HashTable> potentiallyVarScopedFunctionDeclarations,
                boolean hasParameterExpressions
        ) {
            this.freeIdentifiers = freeIdentifiers;
            this.functionScopedDeclarations = functionScopedDeclarations;
            this.blockScopedDeclarations = blockScopedDeclarations;
            this.functionDeclarations = functionDeclarations;
            this.children = children;
            this.dynamic = dynamic;
            this.bindingsForParent = bindingsForParent;
            this.potentiallyVarScopedFunctionDeclarations = potentiallyVarScopedFunctionDeclarations;
            this.hasParameterExpressions = hasParameterExpressions;
        }

        /*
         * Identity constructor
         */
        private State() {
            this.freeIdentifiers = HashTable.emptyUsingEquality();
            this.functionScopedDeclarations = HashTable.emptyUsingEquality();
            this.blockScopedDeclarations = HashTable.emptyUsingEquality();
            this.functionDeclarations = HashTable.emptyUsingEquality();
            this.children = ImmutableList.empty();
            this.dynamic = false;
            this.bindingsForParent = ImmutableList.empty();
            this.potentiallyVarScopedFunctionDeclarations = HashTable.emptyUsingEquality();
            this.hasParameterExpressions = false;
        }

        /*
         * Monoidal append: merges the two states together
         */
        @SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject", "ObjectEquality"})
        private State(@NotNull State a, @NotNull State b) {
            this.freeIdentifiers = a.freeIdentifiers.merge(b.freeIdentifiers, ImmutableList::append);
            this.functionScopedDeclarations = a.functionScopedDeclarations.merge(b.functionScopedDeclarations, ImmutableList::append);
            this.blockScopedDeclarations = a.blockScopedDeclarations.merge(b.blockScopedDeclarations, ImmutableList::append);
            this.functionDeclarations = a.functionDeclarations.merge(b.functionDeclarations, ImmutableList::append);
            this.children = a.children.append(b.children);
            this.dynamic = a.dynamic || b.dynamic;
            this.bindingsForParent = a.bindingsForParent.append(b.bindingsForParent);
            this.potentiallyVarScopedFunctionDeclarations = a.potentiallyVarScopedFunctionDeclarations.merge(b.potentiallyVarScopedFunctionDeclarations, ImmutableList::append);
            this.hasParameterExpressions = a.hasParameterExpressions || b.hasParameterExpressions;
        }


        /*
         * Used when a scope boundary is encountered. It resolves the free identifiers
         * and declarations found into variable objects. Any free identifiers remaining
         * are carried forward into the new state object.
         */
        private State finish(@NotNull Node astNode, @NotNull Scope.Type scopeType) {
            return finish(astNode, scopeType, false, false);
        }

        private State finish(@NotNull Node astNode, @NotNull Scope.Type scopeType, boolean resolveArguments, boolean shouldB33) {
            ImmutableList variables = ImmutableList.empty();

            HashTable> functionScope = HashTable.emptyUsingEquality();
            HashTable> freeIdentifiers = this.freeIdentifiers;
            HashTable> potentiallyVarScopedFunctionDeclarations = this.potentiallyVarScopedFunctionDeclarations;
            ImmutableList children = this.children;

            for (Pair> name :  this.blockScopedDeclarations.entries()) {
                potentiallyVarScopedFunctionDeclarations = potentiallyVarScopedFunctionDeclarations.remove(name.left);
            }
            for (Pair> fdecl :  this.functionDeclarations.entries()) {
                Maybe> maybeConflict = this.potentiallyVarScopedFunctionDeclarations.get(fdecl.left);
                if (maybeConflict.isJust()) {
                    ImmutableList existingDeclarations = maybeConflict.fromJust();
                    ImmutableList newDeclarations = fdecl.right;
                    if (existingDeclarations.length != 1 || existingDeclarations.maybeHead().fromJust().node != newDeclarations.maybeHead().fromJust().node) { // don't conflict with your own lexical declaration
                        potentiallyVarScopedFunctionDeclarations = potentiallyVarScopedFunctionDeclarations.remove(fdecl.left);
                    }
                }
            }


            switch (scopeType) {
                case Block:
                case Catch:
                case With:
                case FunctionName:
                case ClassName:
                case ParameterExpression:
                    // resolve only block-scoped free declarations
                    ImmutableList variables3 = variables;
                    for (Pair> entry2 : this.blockScopedDeclarations.merge(this.functionDeclarations, ImmutableList::append).entries()) {
                        String name2 = entry2.left;
                        ImmutableList declarations2 = entry2.right;
                        ImmutableList references2 = freeIdentifiers.get(name2).orJust(ImmutableList.empty());
                        variables3 = ImmutableList.cons(new Variable(name2, references2, declarations2), variables3);
                        freeIdentifiers = freeIdentifiers.remove(name2);
                    }
                    variables = variables3;
                    functionScope = this.functionScopedDeclarations;
                    break;
                case Parameters:
                case ArrowFunction:
                case Function:
                case Module:
                case Script:
                    // resolve both block-scoped and function-scoped free declarations

                    // first, block-scope declarations
                    HashTable> newDeclarations = this.blockScopedDeclarations;

                    // top-level lexical declarations in scripts are not globals, so create a separate scope for them
                    if (scopeType == Scope.Type.Script) {
                        for (Pair> entry : newDeclarations.entries()) {
                            String name = entry.left;
                            ImmutableList declarations = entry.right;
                            ImmutableList references = freeIdentifiers.get(name).orJust(ImmutableList.empty());
                            variables = ImmutableList.cons(new Variable(name, references, declarations), variables);
                            freeIdentifiers = freeIdentifiers.remove(name);
                        }
                        children = ImmutableList.of(
                                new Scope(children, variables, freeIdentifiers, scopeType, this.dynamic, astNode)
                        );
                        variables = ImmutableList.empty();
                        newDeclarations = HashTable.emptyUsingEquality();
                    }


                    // then, var-scope declarations
                    if (resolveArguments) {
                        newDeclarations = newDeclarations.merge(HashTable.>emptyUsingEquality().put("arguments", ImmutableList.empty()));
                    }
                    newDeclarations = newDeclarations.merge(this.functionScopedDeclarations, ImmutableList::append).merge(this.functionDeclarations, ImmutableList::append);


                    // B.3.3: create an additional var-scoped binding for functions in blocks
                    if (shouldB33) { // todo maybe also script? check bugzilla.
                        newDeclarations = newDeclarations.merge(potentiallyVarScopedFunctionDeclarations, ImmutableList::append);
                    }

                    for (Pair> entry : newDeclarations.entries()) {
                        String name = entry.left;
                        ImmutableList declarations = entry.right;
                        ImmutableList references = freeIdentifiers.get(name).orJust(ImmutableList.empty());
                        variables = ImmutableList.cons(new Variable(name, references, declarations), variables);
                        freeIdentifiers = freeIdentifiers.remove(name);
                    }

                    if (scopeType == Scope.Type.Module) { // no declarations in a module are global
                        children = ImmutableList.of(
                                new Scope(children, variables, freeIdentifiers, scopeType, this.dynamic, astNode)
                        );
                        variables = ImmutableList.empty();
                    }

                    potentiallyVarScopedFunctionDeclarations = HashTable.emptyUsingEquality();
                    break;
                default:
                    throw new RuntimeException("Not reached");
            }

            Scope scope = (scopeType == Scope.Type.Script || scopeType == Scope.Type.Module) ?
                    new GlobalScope(children, variables, freeIdentifiers, astNode) :
                    new Scope(children, variables, freeIdentifiers, scopeType, this.dynamic, astNode);

            return new State(
                    freeIdentifiers, functionScope, HashTable.emptyUsingEquality(), HashTable.emptyUsingEquality(),
                    ImmutableList.of(scope), false, this.bindingsForParent, potentiallyVarScopedFunctionDeclarations, this.hasParameterExpressions);
        }

        /*
         * Observe variables entering scope
         */
        @NotNull
        private State addDeclarations(@NotNull Kind kind) {
            return addDeclarations(kind, false);
        }

        @NotNull
        private State addDeclarations(@NotNull Kind kind, boolean keepBindingsForParent) {
            HashTable> declMap =
                    kind.isBlockScoped ? this.blockScopedDeclarations : this.functionScopedDeclarations;

            for (BindingIdentifier binding : this.bindingsForParent) {
                Declaration decl = new Declaration(binding, kind);
                ImmutableList decls = declMap.get(binding.name).orJust(ImmutableList.empty());
                decls = decls.cons(decl);
                declMap = declMap.put(binding.name, decls);
            }
            return new State(
                    this.freeIdentifiers,
                    kind.isBlockScoped ? this.functionScopedDeclarations : declMap,
                    kind.isBlockScoped ? declMap : this.blockScopedDeclarations,
                    this.functionDeclarations,
                    this.children,
                    this.dynamic,
                    keepBindingsForParent ? this.bindingsForParent : ImmutableList.empty(),
                    this.potentiallyVarScopedFunctionDeclarations,
                    this.hasParameterExpressions
            );
        }

        @NotNull
        private State addFunctionDeclaration() {
            if (this.bindingsForParent.length == 0) { // i.e., this is `export default function () {...}`
                return this;
            }
            BindingIdentifier binding = this.bindingsForParent.index(0).fromJust();
            Declaration decl = new Declaration(binding, Kind.FunctionDeclaration);
            return new State(
                    this.freeIdentifiers,
                    this.functionScopedDeclarations,
                    this.blockScopedDeclarations,
                    HashTable.>emptyUsingEquality().put(binding.name, ImmutableList.of(decl)),
                    this.children,
                    this.dynamic,
                    ImmutableList.empty(),
                    this.potentiallyVarScopedFunctionDeclarations,
                    this.hasParameterExpressions
            );
        }

        /*
         * Observe references
         */
        @NotNull
        public State addReferences(@NotNull Accessibility accessibility) {
            return addReferences(accessibility, false);
        }

        @NotNull
        private State addReferences(@NotNull Accessibility accessibility, boolean keepBindingsForParent) {
            HashTable> free = this.freeIdentifiers;
            for (BindingIdentifier binding : this.bindingsForParent) {
                Reference ref = new Reference(binding, accessibility);
                ImmutableList refs = free.get(binding.name).orJust(ImmutableList.empty());
                refs = refs.cons(ref);
                free = free.put(binding.name, refs);
            }
            return new State(
                    free,
                    this.functionScopedDeclarations,
                    this.blockScopedDeclarations,
                    this.functionDeclarations,
                    this.children,
                    this.dynamic,
                    keepBindingsForParent ? this.bindingsForParent : ImmutableList.empty(),
                    this.potentiallyVarScopedFunctionDeclarations,
                    this.hasParameterExpressions
            );
        }

        @NotNull
        public State taint() {
            return new State(
                    this.freeIdentifiers,
                    this.functionScopedDeclarations,
                    this.blockScopedDeclarations,
                    this.functionDeclarations,
                    this.children,
                    true,
                    this.bindingsForParent,
                    this.potentiallyVarScopedFunctionDeclarations,
                    this.hasParameterExpressions
            );
        }

        @NotNull
        public State withoutBindingsForParent() {
            return new State(
                    this.freeIdentifiers,
                    this.functionScopedDeclarations,
                    this.blockScopedDeclarations,
                    this.functionDeclarations,
                    this.children,
                    this.dynamic,
                    ImmutableList.empty(),
                    this.potentiallyVarScopedFunctionDeclarations,
                    this.hasParameterExpressions
            );
        }

        @NotNull
        public State withParameterExpressions() {
            return new State(
                    this.freeIdentifiers,
                    this.functionScopedDeclarations,
                    this.blockScopedDeclarations,
                    this.functionDeclarations,
                    this.children,
                    this.dynamic,
                    this.bindingsForParent,
                    this.potentiallyVarScopedFunctionDeclarations,
                    true
            );
        }

        @NotNull
        public State withoutParameterExpressions() {
            return new State(
                    this.freeIdentifiers,
                    this.functionScopedDeclarations,
                    this.blockScopedDeclarations,
                    this.functionDeclarations,
                    this.children,
                    this.dynamic,
                    this.bindingsForParent,
                    this.potentiallyVarScopedFunctionDeclarations,
                    false
            );
        }

        @NotNull
        public State withPotentialVarFunctions(@NotNull ImmutableList funcs) {
            HashTable> potentiallyVarScopedFunctionDeclarations = this.potentiallyVarScopedFunctionDeclarations;
            for (BindingIdentifier bi : funcs) {
                ImmutableList existing = potentiallyVarScopedFunctionDeclarations.get(bi.name).orJust(ImmutableList.empty());
                potentiallyVarScopedFunctionDeclarations = potentiallyVarScopedFunctionDeclarations.put(bi.name, existing.cons(new Declaration(bi, Kind.FunctionB33)));
            }
            return new State(
                    this.freeIdentifiers,
                    this.functionScopedDeclarations,
                    this.blockScopedDeclarations,
                    this.functionDeclarations,
                    this.children,
                    this.dynamic,
                    this.bindingsForParent,
                    potentiallyVarScopedFunctionDeclarations,
                    this.hasParameterExpressions
            );
        }
    }

    @SuppressWarnings("ProtectedInnerClass")
    private static final class StateMonoid implements Monoid {
        @Override
        @NotNull
        public State identity() {
            return new State();
        }

        @Override
        @NotNull
        public State append(State a, State b) {
            if (a == b) {
                return a;
            }
            return new State(a, b);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy