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

org.aya.resolve.visitor.StmtPreResolver Maven / Gradle / Ivy

The newest version!
// Copyright (c) 2020-2024 Tesla (Yinsen) Zhang.
// Use of this source code is governed by the MIT license that can be found in the LICENSE.md file.
package org.aya.resolve.visitor;

import kala.collection.SeqView;
import kala.collection.immutable.ImmutableSeq;
import org.aya.resolve.ResolveInfo;
import org.aya.resolve.ResolvingStmt;
import org.aya.resolve.context.ModuleContext;
import org.aya.resolve.context.NoExportContext;
import org.aya.resolve.context.PhysicalModuleContext;
import org.aya.resolve.context.ReporterContext;
import org.aya.resolve.error.NameProblem;
import org.aya.resolve.error.PrimResolveError;
import org.aya.resolve.module.ModuleLoader;
import org.aya.syntax.concrete.stmt.*;
import org.aya.syntax.concrete.stmt.decl.*;
import org.aya.syntax.core.def.AnyDef;
import org.aya.syntax.core.def.PrimDef;
import org.aya.syntax.ref.DefVar;
import org.aya.syntax.ref.QPath;
import org.aya.util.binop.Assoc;
import org.aya.util.binop.OpDecl;
import org.aya.util.error.Panic;
import org.aya.util.reporter.Reporter;
import org.aya.util.reporter.SuppressingReporter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.function.BiConsumer;
import java.util.function.Function;

/**
 * simply adds all top-level names to the context
 */
public record StmtPreResolver(@NotNull ModuleLoader loader, @NotNull ResolveInfo resolveInfo) {
  /**
   * Resolve {@link Stmt}s under {@param context}.
   *
   * @return the context of the body of each {@link Stmt}, where imports and opens are stripped.
   */
  public ImmutableSeq resolveStmt(@NotNull ImmutableSeq stmts, ModuleContext context) {
    return stmts.mapNotNull(stmt -> resolveStmt(stmt, context));
  }

  public @Nullable ResolvingStmt resolveStmt(@NotNull Stmt stmt, @NotNull ModuleContext context) {
    return switch (stmt) {
      case Decl decl -> resolveDecl(decl, context);
      case Command.Module mod -> {
        var wholeModeName = context.modulePath().derive(mod.name());
        // Is there a file level module with path {context.moduleName}::{mod.name} ?
        if (loader.existsFileLevelModule(wholeModeName)) {
          context.reportAndThrow(new NameProblem.ClashModNameError(wholeModeName, mod.sourcePos()));
        }
        var newCtx = context.derive(mod.name());
        var children = resolveStmt(mod.contents(), newCtx);
        context.importModuleContext(ModuleName.This.resolve(mod.name()), newCtx, mod.accessibility(), mod.sourcePos());
        yield new ResolvingStmt.ModStmt(children);
      }
      case Command.Import cmd -> {
        var modulePath = cmd.path();
        var success = loader.load(modulePath);
        if (success == null)
          context.reportAndThrow(new NameProblem.ModNotFoundError(modulePath, cmd.sourcePos()));
        var mod = success.thisModule();
        var as = cmd.asName();
        var importedName = as != null ? ModuleName.This.resolve(as) : modulePath.asName();
        context.importModuleContext(importedName, mod, cmd.accessibility(), cmd.sourcePos());
        var importInfo = new ResolveInfo.ImportInfo(success, cmd.accessibility() == Stmt.Accessibility.Public);
        resolveInfo.imports().put(importedName, importInfo);
        yield null;
      }
      case Command.Open cmd -> {
        var mod = cmd.path();
        var acc = cmd.accessibility();
        var useHide = cmd.useHide();
        var ctx = cmd.openExample() ? exampleContext(context) : context;
        ctx.openModule(mod, acc, cmd.sourcePos(), useHide);
        // open necessities from imported modules (not submodules)
        // because the module itself and its submodules share the same ResolveInfo
        resolveInfo.imports().getOption(mod).ifDefined(modResolveInfo -> {
          if (acc == Stmt.Accessibility.Public) resolveInfo.reExports().put(mod, useHide);
          resolveInfo.open(modResolveInfo.resolveInfo(), cmd.sourcePos(), acc);
        });
        // renaming as infix
        if (useHide.strategy() == UseHide.Strategy.Using) useHide.list().forEach(use -> {
          // skip if there is no `as` or it is qualified.
          if (use.asAssoc() == Assoc.Unspecified) return;
          // In case of qualified, it must be a module, not a definition.
          if (use.id().component() != ModuleName.This) return;
          var symbol = ctx.modules().get(mod).symbols().get(use.id().name());
          var asName = use.asName().getOrDefault(use.id().name());
          var renamedOpDecl = new ResolveInfo.RenamedOpDecl(new OpDecl.OpInfo(asName, use.asAssoc()));
          var bind = use.asBind();
          resolveInfo.renameOp(ctx, AnyDef.fromVar(symbol), renamedOpDecl, bind, true);
        });
        yield null;
      }
      case Generalize variables -> {
        for (var variable : variables.variables)
          context.defineSymbol(variable, Stmt.Accessibility.Private, variable.sourcePos);
        yield new ResolvingStmt.GenStmt(variables);
      }
    };
  }

  private @NotNull ResolvingStmt resolveDecl(@NotNull Decl predecl, @NotNull ModuleContext context) {
    return switch (predecl) {
      case DataDecl decl -> {
        var ctx = resolveTopLevelDecl(decl, context);
        var innerCtx = resolveChildren(decl, ctx, d -> d.body.view(), (con, mCtx) -> {
          setupModule(mCtx, con.ref);
          mCtx.defineSymbol(con.ref(), Stmt.Accessibility.Public, con.sourcePos());
        });
        yield new ResolvingStmt.TopDecl(decl, innerCtx);
      }
      case ClassDecl decl -> {
        var ctx = resolveTopLevelDecl(decl, context);
        var innerCtx = resolveChildren(decl, ctx, d -> d.members.view(), (mem, mCtx) -> {
          setupModule(mCtx, mem.ref);
          mCtx.defineSymbol(mem.ref(), Stmt.Accessibility.Public, mem.ref().concrete.sourcePos());
        });
        yield new ResolvingStmt.TopDecl(decl, innerCtx);
      }
      case FnDecl decl -> {
        var ctx = resolveTopLevelDecl(decl, context);
        var hijackedCtx = new ReporterContext(ctx, suppress(context.reporter(), decl));
        yield new ResolvingStmt.TopDecl(decl, hijackedCtx);
      }
      case PrimDecl decl -> {
        var factory = resolveInfo.primFactory();
        var name = decl.ref.name();
        var sourcePos = decl.sourcePos();
        var primID = PrimDef.ID.find(name);
        if (primID == null) context.reportAndThrow(new PrimResolveError.UnknownPrim(sourcePos, name));
        var lack = factory.checkDependency(primID);
        if (lack.isNotEmpty() && lack.get().isNotEmpty())
          context.reportAndThrow(new PrimResolveError.Dependency(name, lack.get(), sourcePos));
        else if (factory.isForbiddenRedefinition(primID, false))
          context.reportAndThrow(new PrimResolveError.Redefinition(name, sourcePos));
        factory.factory(primID, decl.ref);
        var resolvedCtx = resolveTopLevelDecl(decl, context);
        yield new ResolvingStmt.TopDecl(decl, resolvedCtx);
      }
      default -> Panic.unreachable();
    };
  }

  private static Reporter suppress(@NotNull Reporter reporter, @NotNull Decl decl) {
    if (decl.suppresses.isEmpty()) return reporter;
    var r = new SuppressingReporter(reporter);
    decl.suppresses.forEach(suppress -> {
      switch (suppress) {
        case Shadowing -> r.suppress(NameProblem.ShadowingWarn.class);
      }
    });
    return r;
  }

  /**
   * pre-resolve children of {@param decl}
   *
   * @param decl          the top-level decl
   * @param context       the context where {@paran decl} lives in
   * @param childrenGet   the children of {@param decl}
   * @param childResolver perform resolve on the child of {@param decl}
   * @return the module context of {@param decl}, it should be a sub-module of {@param context}
   */
  private  PhysicalModuleContext resolveChildren(
    @NotNull D decl,
    @NotNull ModuleContext context,
    @NotNull Function> childrenGet,
    @NotNull BiConsumer childResolver
  ) {
    var innerCtx = context.derive(decl.ref().name(), suppress(context.reporter(), decl));
    childrenGet.apply(decl).forEach(child -> childResolver.accept(child, innerCtx));
    var module = decl.ref().name();
    context.importModule(
      ModuleName.This.resolve(module),
      innerCtx.exports,
      decl.accessibility(),
      decl.sourcePos()
    );
    return innerCtx;
  }

  public static @NotNull NoExportContext exampleContext(@NotNull ModuleContext context) {
    return context instanceof PhysicalModuleContext physical ? physical.exampleContext() : Panic.unreachable();
  }

  private  @NotNull ModuleContext
  resolveTopLevelDecl(@NotNull D decl, @NotNull ModuleContext context) {
    var ctx = decl.isExample ? exampleContext(context) : context;
    setupModule(ctx, decl.ref());
    ctx.defineSymbol(decl.ref(), decl.accessibility(), decl.sourcePos());
    return ctx;
  }
  private void setupModule(ModuleContext ctx, DefVar ref) {
    ref.module = new QPath(ctx.modulePath(), resolveInfo.modulePath().size());
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy