diff --git a/base/src/main/java/org/aya/resolve/ResolveInfo.java b/base/src/main/java/org/aya/resolve/ResolveInfo.java index 0a78007737..4af3d0abd0 100644 --- a/base/src/main/java/org/aya/resolve/ResolveInfo.java +++ b/base/src/main/java/org/aya/resolve/ResolveInfo.java @@ -41,8 +41,6 @@ * @param imports importing information, it only contains the modules that is explicitly imported, * should not be confused with the {@code import} in {@link ModuleContext#importModuleContext} * the prim factory will be copied to the current one - * @param reExports re-exporting module, it is {@link ModuleName.Qualified} rather than {@link String} - * because we can re-export a module inside another module without import it. */ @Debug.Renderer(text = "modulePath().toString()") public record ResolveInfo( @@ -53,7 +51,6 @@ public record ResolveInfo( @NotNull AyaBinOpSet opSet, @NotNull MutableMap opRename, @NotNull MutableMap imports, - @NotNull MutableMap reExports, @NotNull MutableGraph depGraph ) { public ResolveInfo( @@ -63,7 +60,7 @@ public ResolveInfo( @NotNull AyaBinOpSet opSet ) { this(thisModule, primFactory, shapeFactory, new GlobalInstanceSet(), opSet, - MutableMap.create(), MutableMap.create(), MutableMap.create(), MutableGraph.create()); + MutableMap.create(), MutableMap.create(), MutableGraph.create()); } public @NotNull TyckState makeTyckState() { return new TyckState(shapeFactory, primFactory); @@ -76,7 +73,13 @@ public ExprTycker newTycker(@NotNull Reporter reporter) { return new ExprTycker(makeTyckState(), new InstanceSet(instancesSet), reporter, modulePath()); } - public record ImportInfo(@NotNull ResolveInfo resolveInfo, boolean reExport) { } + /// @param open only used by serialization, not null if it should be [ResolveInfo#open] + public record ImportInfo( + @NotNull ResolveInfo resolveInfo, + boolean reExport, + @Nullable Stmt.Accessibility open + ) { } + public record OpRenameInfo( @NotNull Context bindCtx, @NotNull RenamedOpDecl renamed, @NotNull BindBlock bind, boolean reExport @@ -107,6 +110,8 @@ public void renameOp( opRename.put(defVar, new OpRenameInfo(bindCtx, renamed, bind, reExport)); } + /// Called when a module opens another imported module, note that [#opSet] and [#shapeFactory] are not affected by + /// {@param acc} public void open(@NotNull ResolveInfo other, @NotNull SourcePos sourcePos, @NotNull Stmt.Accessibility acc) { // open defined operator and their bindings opSet.importBind(other.opSet, sourcePos); diff --git a/base/src/main/java/org/aya/resolve/context/Context.java b/base/src/main/java/org/aya/resolve/context/Context.java index 1af173e26b..904c2a88c1 100644 --- a/base/src/main/java/org/aya/resolve/context/Context.java +++ b/base/src/main/java/org/aya/resolve/context/Context.java @@ -8,10 +8,7 @@ import org.aya.syntax.concrete.stmt.ModuleName; import org.aya.syntax.context.Candidate; import org.aya.syntax.context.ContextView; -import org.aya.syntax.ref.AnyVar; -import org.aya.syntax.ref.GenerateKind; -import org.aya.syntax.ref.LocalVar; -import org.aya.syntax.ref.ModulePath; +import org.aya.syntax.ref.*; import org.aya.util.position.SourcePos; import org.aya.util.reporter.Reporter; import org.jetbrains.annotations.NotNull; @@ -103,7 +100,8 @@ default Context bind(@NotNull LocalVar ref, @NotNull Predicate<@Nullable Candida return derive(new ModulePath(ImmutableSeq.of(extraName))); } + /// Note that this won't increase [QPath#fileLevelSize] default @NotNull ModuleContext derive(@NotNull ModulePath extraName) { - return new PhysicalModuleContext(this, modulePath().derive(extraName)); + return new PhysicalModuleContext(this, qualifiedPath().derive(extraName)); } } diff --git a/base/src/main/java/org/aya/resolve/context/EmptyContext.java b/base/src/main/java/org/aya/resolve/context/EmptyContext.java index 04b49474d7..d740a10e31 100644 --- a/base/src/main/java/org/aya/resolve/context/EmptyContext.java +++ b/base/src/main/java/org/aya/resolve/context/EmptyContext.java @@ -8,6 +8,7 @@ import org.aya.syntax.context.ModuleExport; import org.aya.syntax.ref.AnyVar; import org.aya.syntax.ref.ModulePath; +import org.aya.syntax.ref.QPath; import org.aya.util.position.SourcePos; import org.aya.util.reporter.Reporter; import org.jetbrains.annotations.NotNull; @@ -29,10 +30,10 @@ public record EmptyContext(@NotNull Path underlyingFile) implements Context { ) { return null; } @Override public @NotNull PhysicalModuleContext derive(@NotNull ModulePath extraName) { - return new PhysicalModuleContext(this, extraName); + return new PhysicalModuleContext(this, QPath.fileLevel(extraName)); } - @Override public @NotNull ModulePath modulePath() { + @Override public @NotNull QPath qualifiedPath() { throw new UnsupportedOperationException(); } diff --git a/base/src/main/java/org/aya/resolve/context/NoExportContext.java b/base/src/main/java/org/aya/resolve/context/NoExportContext.java index c6224d3682..59365bd827 100644 --- a/base/src/main/java/org/aya/resolve/context/NoExportContext.java +++ b/base/src/main/java/org/aya/resolve/context/NoExportContext.java @@ -9,6 +9,7 @@ import org.aya.syntax.context.ModuleSymbol; import org.aya.syntax.ref.AnyVar; import org.aya.syntax.ref.ModulePath; +import org.aya.syntax.ref.QPath; import org.jetbrains.annotations.NotNull; import java.nio.file.Path; @@ -18,14 +19,14 @@ public record NoExportContext( @NotNull Context parent, @NotNull ModuleSymbol symbols, @NotNull MutableMap modules, - @Override @NotNull ModulePath modulePath + @Override @NotNull QPath qualifiedPath ) implements ModuleContext { public NoExportContext( @NotNull Context parent, @NotNull ModuleSymbol symbols, @NotNull MutableMap modules ) { - this(parent, symbols, modules, parent.modulePath().derive(":NoExport")); + this(parent, symbols, modules, parent.qualifiedPath().derive(":NoExport")); } public NoExportContext(@NotNull Context parent) { @@ -33,5 +34,5 @@ public NoExportContext(@NotNull Context parent) { } @Override public @NotNull Path underlyingFile() { return parent.underlyingFile(); } - @Override public @NotNull ModuleExport exports() { return new ModuleExport(); } + @Override public @NotNull ModuleExport exports() { return new ModuleExport(qualifiedPath); } } diff --git a/base/src/main/java/org/aya/resolve/context/PhysicalModuleContext.java b/base/src/main/java/org/aya/resolve/context/PhysicalModuleContext.java index 70f1e805c1..71a0be46b0 100644 --- a/base/src/main/java/org/aya/resolve/context/PhysicalModuleContext.java +++ b/base/src/main/java/org/aya/resolve/context/PhysicalModuleContext.java @@ -10,7 +10,7 @@ import org.aya.syntax.context.ModuleSymbol; import org.aya.syntax.ref.AnyDefVar; import org.aya.syntax.ref.AnyVar; -import org.aya.syntax.ref.ModulePath; +import org.aya.syntax.ref.QPath; import org.aya.util.position.SourcePos; import org.aya.util.reporter.Reporter; import org.jetbrains.annotations.NotNull; @@ -23,17 +23,18 @@ */ public class PhysicalModuleContext implements ModuleContext { public final @NotNull Context parent; - public final @NotNull ModuleExport exports = new ModuleExport(); + public final @NotNull ModuleExport exports; public final @NotNull ModuleSymbol symbols = new ModuleSymbol<>(); public final @NotNull MutableMap modules = MutableHashMap.create(); - private final @NotNull ModulePath modulePath; - @Override public @NotNull ModulePath modulePath() { return modulePath; } + private final @NotNull QPath qualifiedPath; + @Override public @NotNull QPath qualifiedPath() { return qualifiedPath; } private @Nullable NoExportContext exampleContext; - public PhysicalModuleContext(@NotNull Context parent, @NotNull ModulePath modulePath) { + public PhysicalModuleContext(@NotNull Context parent, @NotNull QPath qualifiedPath) { this.parent = parent; - this.modulePath = modulePath; + this.qualifiedPath = qualifiedPath; + this.exports = new ModuleExport(qualifiedPath); } @Override public boolean importModule( diff --git a/base/src/main/java/org/aya/resolve/visitor/StmtPreResolver.java b/base/src/main/java/org/aya/resolve/visitor/StmtPreResolver.java index 04e9983f50..3834119686 100644 --- a/base/src/main/java/org/aya/resolve/visitor/StmtPreResolver.java +++ b/base/src/main/java/org/aya/resolve/visitor/StmtPreResolver.java @@ -90,7 +90,7 @@ public ImmutableSeq resolveStmt(@NotNull ImmutableSeq stmts context.importModuleContext(importedName, mod, cmd.accessibility(), cmd.sourcePos(), thisReporter); resolveInfo.primFactory().importFrom(success.primFactory()); - var importInfo = new ResolveInfo.ImportInfo(success, cmd.accessibility() == Stmt.Accessibility.Public); + var importInfo = new ResolveInfo.ImportInfo(success, cmd.accessibility() == Stmt.Accessibility.Public, null); resolveInfo.imports().put(importedName, importInfo); yield null; } @@ -103,15 +103,13 @@ public ImmutableSeq resolveStmt(@NotNull ImmutableSeq stmts var success = ctx.openModule(mod, acc, cmd.sourcePos(), useHide, thisReporter); if (!success) yield null; - // store top-level re-exports - // FIXME: this is not enough, because submodule export definitions are not stored - if (ctx == resolveInfo.thisModule()) { - if (acc == Stmt.Accessibility.Public) resolveInfo.reExports().put(mod, 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 -> - resolveInfo.open(modResolveInfo.resolveInfo(), cmd.sourcePos(), acc)); + resolveInfo.imports().getOption(mod).ifDefined(modResolveInfo -> { + // TODO: open regardless the strategy(use / hide)? the user may be able to refer hidden data by `shapeFactory` + resolveInfo.open(modResolveInfo.resolveInfo(), cmd.sourcePos(), acc); + resolveInfo.imports().put(mod, new ResolveInfo.ImportInfo(modResolveInfo.resolveInfo(), modResolveInfo.reExport(), acc)); + }); // renaming as infix if (useHide.strategy() == UseHide.Strategy.Using) useHide.list().forEach(use -> { diff --git a/cli-impl/src/main/java/org/aya/cli/interactive/ReplCompiler.java b/cli-impl/src/main/java/org/aya/cli/interactive/ReplCompiler.java index e999aa8211..b5d877e163 100644 --- a/cli-impl/src/main/java/org/aya/cli/interactive/ReplCompiler.java +++ b/cli-impl/src/main/java/org/aya/cli/interactive/ReplCompiler.java @@ -182,7 +182,7 @@ private void loadFile(@NotNull Path file) { private @NotNull ResolveInfo makeResolveInfo(@NotNull ModuleContext ctx) { var resolveInfo = new ResolveInfo(ctx, tcState.primFactory, tcState.shapeFactory, opSet); imports.forEach(ii -> resolveInfo.imports().put( - ii.modulePath().asName(), new ResolveInfo.ImportInfo(ii, false))); + ii.modulePath().asName(), new ResolveInfo.ImportInfo(ii, false, null))); return resolveInfo; } diff --git a/cli-impl/src/main/java/org/aya/cli/interactive/ReplContext.java b/cli-impl/src/main/java/org/aya/cli/interactive/ReplContext.java index 9abc1f9688..6c9f2d343c 100644 --- a/cli-impl/src/main/java/org/aya/cli/interactive/ReplContext.java +++ b/cli-impl/src/main/java/org/aya/cli/interactive/ReplContext.java @@ -16,10 +16,7 @@ import org.aya.syntax.concrete.stmt.Stmt; import org.aya.syntax.context.ModuleExport; import org.aya.syntax.context.ModuleSymbol; -import org.aya.syntax.ref.AnyDefVar; -import org.aya.syntax.ref.AnyVar; -import org.aya.syntax.ref.DefVar; -import org.aya.syntax.ref.ModulePath; +import org.aya.syntax.ref.*; import org.aya.util.RepoLike; import org.aya.util.position.SourcePos; import org.aya.util.reporter.Reporter; @@ -34,7 +31,8 @@ public final class ReplContext extends PhysicalModuleContext implements RepoLike private @Nullable ImmutableMap moduleTree = null; public ReplContext(@NotNull Context parent, @NotNull ModulePath name) { - super(parent, name); + // QPath is used for serialization only + super(parent, QPath.fileLevel(name)); } @Override public boolean importSymbol( diff --git a/jit-compiler/src/main/java/org/aya/compiler/CompiledModule.java b/jit-compiler/src/main/java/org/aya/compiler/CompiledModule.java index d12e4396bc..921cdd3b23 100644 --- a/jit-compiler/src/main/java/org/aya/compiler/CompiledModule.java +++ b/jit-compiler/src/main/java/org/aya/compiler/CompiledModule.java @@ -4,54 +4,52 @@ import kala.collection.immutable.ImmutableMap; import kala.collection.immutable.ImmutableSeq; -import kala.collection.immutable.ImmutableSet; +import kala.collection.mutable.MutableLinkedHashMap; +import kala.collection.mutable.MutableList; import kala.collection.mutable.MutableMap; import kala.tuple.Tuple; import org.aya.compiler.serializers.AyaSerializer; import org.aya.compiler.serializers.NameSerializer; +import org.aya.generic.AyaDocile; +import org.aya.pretty.doc.Doc; import org.aya.resolve.ResolveInfo; import org.aya.resolve.context.PhysicalModuleContext; import org.aya.resolve.module.ModuleLoader; import org.aya.resolve.salt.AyaBinOpSet; import org.aya.states.primitive.PrimFactory; import org.aya.states.primitive.ShapeFactory; -import org.aya.syntax.compile.JitData; -import org.aya.syntax.compile.JitDef; -import org.aya.syntax.compile.JitFn; -import org.aya.syntax.compile.JitPrim; +import org.aya.syntax.compile.*; import org.aya.syntax.concrete.stmt.*; +import org.aya.syntax.context.ModuleExport; import org.aya.syntax.core.def.AnyDef; import org.aya.syntax.core.def.PrimDef; import org.aya.syntax.core.def.TyckDef; import org.aya.syntax.core.repr.AyaShape; import org.aya.syntax.core.repr.ShapeRecognition; -import org.aya.syntax.ref.CompiledVar; -import org.aya.syntax.ref.ModulePath; -import org.aya.syntax.ref.QName; -import org.aya.syntax.ref.QPath; +import org.aya.syntax.ref.*; import org.aya.util.ArrayUtil; import org.aya.util.Panic; +import org.aya.util.PrettierOptions; import org.aya.util.binop.OpDecl; import org.aya.util.position.SourcePos; -import org.aya.util.position.WithPos; import org.aya.util.reporter.Reporter; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.Serializable; import java.util.EnumMap; /// The .ayac file representation. /// -/// @param imports The modules that this ayac imports. Absolute path. -/// @param exports Whether certain definition is exported. Re-exported symbols will not be here. -/// @param importReExports key: an imported module that is in {@param imports} -/// @param localReExports key: a module defined in this module +/// @param moduleExport the module export of this file level module +/// @param importOpen all module that is imported and opened by this file level module, this is kinda tricky, see [ResolveInfo#open] +/// @param serOps [SerBind] of definitions in this file level module +/// @param opRename [SerRenamedOp] (basically a [SerBind]) of renamed symbols from other modules +/// /// @author kiva public record CompiledModule( - @NotNull ImmutableSeq imports, - @NotNull ImmutableSet exports, - @NotNull ImmutableMap importReExports, - @NotNull ImmutableMap localReExports, + @NotNull ImmutableMap moduleExport, + @NotNull ImmutableSeq importOpen, @NotNull ImmutableMap serOps, @NotNull EnumMap primDefs, @NotNull ImmutableMap opRename @@ -83,49 +81,38 @@ private static Object getJitDef(Class clazz) { } } - record SerBind(@NotNull ImmutableSeq loosers, @NotNull ImmutableSeq tighters) implements Serializable { - public static final SerBind EMPTY = new SerBind(ImmutableSeq.empty(), ImmutableSeq.empty()); - } + record SerModuleExport( + @NotNull ImmutableMap symbols, + @NotNull ImmutableMap modules + ) implements Serializable, AyaDocile { + @Override + public @NotNull Doc toDoc(@NotNull PrettierOptions options) { + var docs = MutableList.create(); - record SerRenamedOp(@NotNull OpDecl.OpInfo info, @NotNull SerBind bind) implements Serializable { } + symbols.forEach((name, qname) -> + docs.append(Doc.sep( + Doc.plain("Definition"), Doc.plain(name), + Doc.plain("as"), Doc.plain(qname.toString())))); - /** - * @param rename not empty - */ - record SerImport( - @NotNull ModulePath path, @NotNull ImmutableSeq rename, - boolean isPublic) implements Serializable { } + modules.forEach((name, qpath) -> + docs.append(Doc.sep( + Doc.plain("Module"), Doc.plain(name.toString()), + Doc.plain("as"), Doc.plain(qpath.toString())))); - record SerQualifiedID(@NotNull ModuleName component, @NotNull String name) implements Serializable { - public static @NotNull SerQualifiedID from(@NotNull QualifiedID qid) { - return new SerQualifiedID(qid.component(), qid.name()); + return Doc.vcat(docs); } - public @NotNull QualifiedID make() { return new QualifiedID(SourcePos.SER, component, name); } } - /// @see UseHide.Rename - record SerRename(@NotNull SerQualifiedID qid, @NotNull String to) implements Serializable { - public static @NotNull SerRename from(@NotNull UseHide.Rename rename) { - return new SerRename(SerQualifiedID.from(rename.name()), rename.to()); - } - public @NotNull UseHide.Rename make() { return new UseHide.Rename(qid.make(), to); } + record SerImportOpen(@NotNull ModulePath path, boolean isPublic) implements Serializable { + } - /// @see UseHide - record SerUseHide( - boolean isUsing, - @NotNull ImmutableSeq names, - @NotNull ImmutableSeq renames - ) implements Serializable { - public static @NotNull SerUseHide from(@NotNull UseHide useHide) { - return new SerUseHide( - useHide.strategy() == UseHide.Strategy.Using, - useHide.list().map(x -> SerQualifiedID.from(x.id())), - useHide.renaming().map(it -> SerRename.from(it.data())) - ); - } + record SerBind(@NotNull ImmutableSeq loosers, @NotNull ImmutableSeq tighters) implements Serializable { + public static final SerBind EMPTY = new SerBind(ImmutableSeq.empty(), ImmutableSeq.empty()); } + record SerRenamedOp(@NotNull OpDecl.OpInfo info, @NotNull SerBind bind) implements Serializable { } + public static @NotNull CompiledModule from(@NotNull ResolveInfo resolveInfo, @NotNull ImmutableSeq defs) { if (!(resolveInfo.thisModule() instanceof PhysicalModuleContext ctx)) { return Panic.unreachable(); @@ -134,19 +121,16 @@ record SerUseHide( var serialization = new Serialization(resolveInfo, MutableMap.create()); defs.forEach(serialization::serOp); - var exports = ctx.exports().symbols().keysView(); - - var imports = resolveInfo.imports().view().map((k, v) -> - new SerImport(v.resolveInfo().modulePath(), - k.ids(), v.reExport())).toSeq(); - var serExport = ImmutableSet.from(exports); - var importReExports = MutableMap.create(); - var localReExports = MutableMap.create(); - resolveInfo.reExports().forEach((qualified, useHide) -> { - var imported = resolveInfo.imports().getOrNull(qualified); - if (imported != null) importReExports.put(imported.resolveInfo().modulePath(), SerUseHide.from(useHide)); - else localReExports.put(qualified, SerUseHide.from(useHide)); + // TODO: i guess we can obtain module path from exports... + var moduleExport = serializeModuleExport(resolveInfo.modulePath(), resolveInfo.thisModule().exports()); + var importOpen = MutableList.create(); + resolveInfo.imports().forEach((_, info) -> { + var acc = info.open(); + if (acc != null) { + importOpen.append(new SerImportOpen(info.resolveInfo().modulePath(), acc == Stmt.Accessibility.Public)); + } }); + var serOps = ImmutableMap.from(serialization.serOps); record RenameData(boolean reExport, QName name, SerRenamedOp renamed) { } var opRename = ImmutableMap.from(resolveInfo.opRename().view().map((k, v) -> { @@ -159,12 +143,172 @@ record RenameData(boolean reExport, QName name, SerRenamedOp renamed) { } .map(data -> Tuple.of(data.name, data.renamed))); var prims = resolveInfo.primFactory().qnameMap(); - return new CompiledModule(imports, serExport, - ImmutableMap.from(importReExports), - ImmutableMap.from(localReExports), + return new CompiledModule( + moduleExport, importOpen.toSeq(), serOps, prims, opRename); } + /// Serialize file level [ModuleExport] to ([ModuleName], [SerModuleExport]) pairs + private static @NotNull ImmutableMap serializeModuleExport( + @NotNull ModulePath thisModulePath, + @NotNull ModuleExport export + ) { + var definedModules = MutableMap.create(); + + var queue = MutableLinkedHashMap.of(ModuleName.This, export); + + // sub modules are always public, thus we can find them in the [ModuleExport] + export.modules().forEach((_, mod) -> { + if (mod.qualifiedPath().fileModule().equals(thisModulePath)) { + var name = mod.qualifiedPath().module().removePrefix(thisModulePath); + // guaranteed by the `if` condition + assert name != null; + queue.put(name, mod); + } + }); + + queue.forEach((modName, mod) -> { + var symbols = MutableMap.create(); + var modules = MutableMap.create(); + + mod.symbols().forEach((name, def) -> { + symbols.put(name, AnyDef.fromVar(def).qualifiedName()); + }); + + mod.modules().forEach((name, myMod) -> { + modules.put(name, myMod.qualifiedPath()); + }); + + definedModules.put(modName, new SerModuleExport( + ImmutableMap.from(symbols), + ImmutableMap.from(modules))); + }); + + return ImmutableMap.from(definedModules); + } + + public static class MyModuleLoader { + public final @NotNull ModuleLoader loader; + public final @NotNull DeState state; + /// ModulePath to currently deserializing module + public final @NotNull ModulePath thisModulePath; + public final @NotNull MutableMap cache = MutableMap.create(); + // all deserialized submodule in this file level module + public final @NotNull MutableMap subModules = MutableMap.create(); + public final @NotNull ImmutableMap thisDefs; + + public MyModuleLoader( + @NotNull ModuleLoader loader, + @NotNull DeState state, + @NotNull ModulePath thisModulePath, + @NotNull ImmutableSeq thisDefs + ) { + this.loader = loader; + this.state = state; + this.thisModulePath = thisModulePath; + this.thisDefs = thisDefs.associateBy(JitUnit::qualifiedName); + } + + private @NotNull ModuleExport loadFileLevel(@NotNull QPath path) { + var key = path.fileModule(); + var exists = cache.getOrNull(key); + if (exists != null) return exists.thisModule().exports(); + + // TODO: handle error + var loaded = loader.load(path.fileModule()).get(); + cache.put(key, loaded); + return loaded.thisModule().exports(); + } + + // Load ANY module, including submodule + public @NotNull ModuleExport load(@NotNull QPath path) { + // loading a module that previously deserialized. + if (path.fileModule().equals(thisModulePath)) { + switch (path.localModule()) { + case ModuleName.Qualified qualified -> subModules.get(qualified); // never fail, i guess + case ModuleName.ThisRef _ -> { + // should be unreachable, there is no way to reference the file module itself in itself. + return Panic.unreachable(); + } + } + } + + var fileLevel = loadFileLevel(path); + var name = path.localModule(); + return switch (name) { + case ModuleName.ThisRef _ -> fileLevel; + case ModuleName.Qualified qualified -> + // never fail, unless the core is corrupted. maybe we can provide some useful information in that case? + fileLevel.modules().get(qualified); + }; + } + + /// Load any public definition + public @NotNull CompiledVar load(@NotNull QName name) { + if (name.module().fileModule().equals(thisModulePath)) { + return new CompiledVar(this.thisDefs.get(name)); // should not fail + } + + // sanity check, even we can just return a AnyDefVar + return (CompiledVar) load(name.module()).symbols().get(name.name()); + } + + /// Called when a submodule is deserialized + public void acceptSubmodule(@NotNull ModuleName.Qualified name, @NotNull ModuleExport export) { + assert export.qualifiedPath() + .equals(QPath.fileLevel(thisModulePath).derive(name)); + + var exists = this.subModules.put(name, export); + assert exists.isEmpty(); + } + } + + /// Deserialize [#thisModulePath] from [#modules], if `thisModulePath` is a sub module, + /// then deserialized module must be put to [#loader]. + private @NotNull ModuleExport deserializeModuleExport( + @NotNull QPath thisModulePath, + @NotNull MyModuleLoader loader, + @NotNull ImmutableMap modules + ) { + @Nullable ModuleName.Qualified subModuleName = null; + + switch (thisModulePath.localModule()) { + case ModuleName.Qualified qualified -> subModuleName = qualified; + case ModuleName.ThisRef _ -> { } + } + + if (subModuleName != null) { + var cache = loader.subModules.getOrNull(subModuleName); + if (cache != null) return cache; + } + + var export = new ModuleExport(thisModulePath); + var thisSer = modules.get(thisModulePath.localModule()); + thisSer.symbols().forEach((name, def) -> { + var symbol = loader.load(def); + export.symbols().put(name, symbol); + }); + + thisSer.modules().forEach((name, modPath) -> { + if (modPath.fileModule().equals(loader.thisModulePath)) { + // the call graph is equal to the dependency graph, which is a DAG + var deser = deserializeModuleExport(modPath, loader, modules); + export.modules().put(name, deser); + } else { + // otherwise, we just load module + var mod = loader.load(modPath); + export.modules().put(name, mod); + } + }); + + if (subModuleName != null) { + loader.acceptSubmodule(subModuleName, export); + } + + return export; + } + + // TODO: rename this private record Serialization( @NotNull ResolveInfo resolveInfo, @NotNull MutableMap serOps @@ -196,38 +340,56 @@ private void serOp(@NotNull TyckDef def) { @NotNull PrimFactory primFactory, @NotNull ShapeFactory shapeFactory, @NotNull Reporter reporter ) { var resolveInfo = new ResolveInfo(context, primFactory, shapeFactory, new AyaBinOpSet(reporter)); - shallowResolve(loader, resolveInfo, reporter); + var allDefs = MutableList.create(); var rootClass = state.topLevelClass(context.modulePath()); for (var jitClass : rootClass.getDeclaredClasses()) { - loadModule(primFactory, shapeFactory, context, jitClass, reporter); + var object = DeState.getJitDef(jitClass); + if (!(object instanceof JitDef jitDef)) continue; + allDefs.append(jitDef); + loadDefInfo(primFactory, shapeFactory, jitDef); } + + var myLoader = new MyModuleLoader(loader, state, context.modulePath(), allDefs.toSeq()); + var root = deserializeModuleExport(context.qualifiedPath(), + myLoader, this.moduleExport); + + // kinda slow, but who cares?? + context.exports.symbols().putAll(root.symbols()); + context.exports.modules().putAll(root.modules()); + + // perform ResolveInfo#open + this.importOpen.forEach(importOpen -> { + var exists = myLoader.cache.getOrNull(importOpen.path); + + if (exists == null) { + // in case the module didn't public open the imported module + // TODO: handle error + var result = loader.load(importOpen.path); + var err = result.getErrOrNull(); + if (err != null) { + throw new Panic("Failed to load module " + importOpen.path + " that is referred by a compiled aya since: " + err + "."); + } else { + exists = result.get(); + } + } + + resolveInfo.open(exists, SourcePos.SER, importOpen.isPublic ? Stmt.Accessibility.Public : Stmt.Accessibility.Private); + }); + + // FIXME: will this overlap with `definePrim` in `loadModule` ? primDefs.forEach((_, qname) -> primFactory.definePrim((JitPrim) state.resolve(qname))); + deOp(state, resolveInfo); return resolveInfo; } - private void loadModule( - @NotNull PrimFactory primFactory, @NotNull ShapeFactory shapeFactory, - @NotNull PhysicalModuleContext context, @NotNull Class jitClass, @NotNull Reporter reporter + private void loadDefInfo( + @NotNull PrimFactory primFactory, @NotNull ShapeFactory shapeFactory, @NotNull JitDef jitDef ) { - var object = DeState.getJitDef(jitClass); - // Not all JitUnit are JitDef, see JitMatchy - if (!(object instanceof JitDef jitDef)) return; var metadata = jitDef.metadata(); - export(context, jitDef); switch (jitDef) { case JitData data -> { - // The accessibility doesn't matter, this context is readonly - var innerCtx = context.derive(data.name()); - for (var constructor : data.constructors()) { - var success = innerCtx.defineSymbol(new CompiledVar(constructor), Stmt.Accessibility.Public, SourcePos.SER, reporter); - if (!success) Panic.unreachable(); - } - var success = context.importModuleContext( - ModuleName.This.resolve(data.name()), - innerCtx, Stmt.Accessibility.Public, SourcePos.SER, reporter); - if (!success) Panic.unreachable(); if (metadata.shape() != -1) { var recognition = new ShapeRecognition(AyaShape.values()[metadata.shape()], ImmutableMap.from(ArrayUtil.zip(metadata.recognition(), @@ -247,39 +409,6 @@ private void loadModule( } } - /// like [org.aya.resolve.visitor.StmtPreResolver] but only resolve import - private void shallowResolve(@NotNull ModuleLoader loader, @NotNull ResolveInfo thisResolve, @NotNull Reporter reporter) { - for (var anImport : imports) { - var modName = anImport.path; - var modRename = ModuleName.qualified(anImport.rename); - var isPublic = anImport.isPublic; - - var loaded = loader.load(modName) - .getOrThrow(() -> new Panic("Unable to load a dependency module of a compiled module")); - - thisResolve.imports().put(modRename, new ResolveInfo.ImportInfo(loaded, isPublic)); - var mod = loaded.thisModule(); - var success = thisResolve.thisModule() - .importModuleContext(modRename, mod, isPublic ? Stmt.Accessibility.Public : Stmt.Accessibility.Private, SourcePos.SER, reporter); - if (!success) Panic.unreachable(); - var useHide = importReExports.getOrNull(modName); - if (useHide != null) { - success = thisResolve.thisModule().openModule(modRename, - Stmt.Accessibility.Public, - useHide.names().map(SerQualifiedID::make), - useHide.renames().map(x -> new WithPos<>(SourcePos.SER, x.make())), - SourcePos.SER, useHide.isUsing() ? UseHide.Strategy.Using : UseHide.Strategy.Hiding, - reporter); - - if (!success) Panic.unreachable(); - } - var acc = importReExports.containsKey(modName) - ? Stmt.Accessibility.Public - : Stmt.Accessibility.Private; - thisResolve.open(loaded, SourcePos.SER, acc); - } - } - /** * like {@link org.aya.resolve.visitor.StmtResolver} but only resolve operator */ @@ -319,34 +448,4 @@ private void deBindDontCare( private @NotNull OpDecl resolveOp(@NotNull ResolveInfo resolveInfo, @NotNull CompiledModule.DeState state, @NotNull QName name) { return resolveInfo.resolveOpDecl(state.resolve(name)); } - - /// @see org.aya.syntax.context.ModuleExport#map - /// @see org.aya.syntax.context.ModuleExport#filter - private void export(@NotNull PhysicalModuleContext context, @NotNull JitDef def) { - boolean success = true; - var module = def.qualifiedName().module().localModule(); - if (module instanceof ModuleName.ThisRef && exports.contains(def.name())) { - success = context.exportSymbol(def.name(), new CompiledVar(def)); - } - for (int i = 0; i < module.length(); ++i) { - var qualified = new ModuleName.Qualified(module.ids().drop(i)); - var local = localReExports.getOrNull(qualified); - if (local == null) continue; - var contains = local.names.find(qid -> - qid.name.contentEquals(def.name()) && - qualified.concat(qid.component).equals(module)) - .getOrNull(); - if (local.isUsing && contains != null) { - var rename = local.renames.find(it -> it.qid.equals(contains)).getOrNull(); - if (rename != null) { - success = context.exportSymbol(rename.to, new CompiledVar(def)) && success; - } else { - success = context.exportSymbol(def.name(), new CompiledVar(def)) && success; - } - } else if (!local.isUsing && contains == null) { - success = context.exportSymbol(def.name(), new CompiledVar(def)) && success; - } - } - assert success : "DuplicateExportError should not happen in CompiledModule"; - } } diff --git a/jit-compiler/src/test/java/ResolveTest.java b/jit-compiler/src/test/java/ResolveTest.java new file mode 100644 index 0000000000..de2312bf90 --- /dev/null +++ b/jit-compiler/src/test/java/ResolveTest.java @@ -0,0 +1,73 @@ +// Copyright (c) 2020-2025 Tesla (Yinsen) Zhang. +// Use of this source code is governed by the MIT license that can be found in the LICENSE.md file. + +import org.aya.compiler.CompiledModule; +import org.aya.prettier.AyaPrettierOptions; +import org.aya.resolve.context.EmptyContext; +import org.aya.resolve.context.PhysicalModuleContext; +import org.aya.resolve.module.DumbModuleLoader; +import org.aya.states.primitive.PrimFactory; +import org.aya.util.FileUtil; +import org.aya.util.reporter.ThrowingReporter; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; + +public class ResolveTest { + @Test + public void test() throws IOException { + var result = CompileTest.tyck(""" + open inductive Unit | tt + private def privateDef : Unit => tt + def publicDef : Unit => tt + + module Sub { + private def privateDefInSub : Unit => tt + def publicDefInSub : Unit => tt + } + + module Sub2 { + def publicDefInSub2 : Unit => tt + } + + module Sub3 { + def infixr publicDefInSub3 Unit Unit : Unit => tt + } + + module Sub4 { + open Sub3 + def publicDefInSub4 : Unit => tt + } + + module Sub5 { + def publicDefInSub5 Unit Unit : Unit => tt + } + + open Sub2 + open Sub5 using (publicDefInSub5 as infixl *) + """); + + var info = result.info(); + var compiledModule = CompiledModule.from(info, result.defs()); + + var base = CompileTest.GEN_DIR.resolve("resolveTest"); + + FileUtil.deleteRecursively(base); + CompileTest.serializeFrom(result, base); + + try (var innerLoader = new URLClassLoader(new URL[]{base.toUri().toURL()}, getClass().getClassLoader())) { + var reporter = new ThrowingReporter(AyaPrettierOptions.debug()); + var baseCtx = (PhysicalModuleContext) new EmptyContext(Path.of("baka")) + .derive(DumbModuleLoader.DUMB_MODULE_STRING); + var deser = compiledModule.toResolveInfo( + new DumbModuleLoader(new ThrowingReporter(AyaPrettierOptions.debug()), baseCtx), + baseCtx, innerLoader, new PrimFactory(), reporter + ); + + return; + } + } +} diff --git a/note/module2.md b/note/module2.md new file mode 100644 index 0000000000..7cfbc9a70f --- /dev/null +++ b/note/module2.md @@ -0,0 +1,33 @@ +鉴于目前 module 系统有点问题,重新梳理一遍 module 相关的代码。 + +## 语法层面 + +```aya +public open import foo::bar using (aaa as bbb) +``` + +会被解糖为 + +```aya +public import foo::bar +public open foo::bar using (aaa as bbb) +``` + +也就是说,被 import 进来的模块在被重新 re-export 的时候(注意这里 re-export 的是模块而不是它里面的东西),不会被修改。 +因此我们可以用一个全局 id `QPath` 来引用每个模块。 + +同时,对于局部模块声明,它一定是 public 的,也就是说一定会被 export,这意味着我们有稳定的方式找到一个局部模块。 +并且局部模块一定不会与文件级别的模块重名,比如 `foo/bar.aya` 和局部模块 `bar` in `foo.aya` 不会同时存在。 +因此,我们可以用 `QPath` 来定位任意模块,并且通过 `load` 文件级别的模块,再获取局部模块来找到任意模块。 + +## `open` + +在 `open` 一个模块时,会发生如下事情: + +* 被 `open` 的模块的 `export` 内容被加载到当前模块的 `ModuleContext` 中,如果是 `public open`,那么同时还会被加载到当前模块的 + `export` 中。 +* 如果该 `open` 包含重命名,并且该重命名的结果是一个运算符,那么会将该重命名存放在 `ResolveInfo#opRename` 中,并在之后的 + `StmtBinder` 中被应用到 `opSet` 上。 +* 被 `open` 的模块 (ResolveInfo) 的 `shapeFactory`, `opSet` 和 `opRename` 会被导入。 + (注意 `opSet` 和 `opRename` 是高度绑定的,可以将 `opSet` 看作是对 `opRename` 求值后的结果, + 并且应该有 `opSet0 + opSet1 = eval(opRename0) + eval(opRename1) = eval(opRename0 + opRename1)`) diff --git a/syntax/src/main/java/org/aya/syntax/context/ContextView.java b/syntax/src/main/java/org/aya/syntax/context/ContextView.java index f14e49264e..44f5ef704c 100644 --- a/syntax/src/main/java/org/aya/syntax/context/ContextView.java +++ b/syntax/src/main/java/org/aya/syntax/context/ContextView.java @@ -9,6 +9,7 @@ import org.aya.syntax.ref.AnyVar; import org.aya.syntax.ref.LocalVar; import org.aya.syntax.ref.ModulePath; +import org.aya.syntax.ref.QPath; import org.aya.util.position.SourcePos; import org.aya.util.reporter.Reporter; import org.jetbrains.annotations.NotNull; @@ -42,11 +43,15 @@ default MutableList collect(@NotNull MutableList container) return container; } - /// The path of this module - default @NotNull ModulePath modulePath() { + default @NotNull QPath qualifiedPath() { var p = parent(); assert p != null; - return p.modulePath(); + return p.qualifiedPath(); + } + + /// The path of this module + default @NotNull ModulePath modulePath() { + return qualifiedPath().module(); } /// Getting a symbol by name {@param name}. diff --git a/syntax/src/main/java/org/aya/syntax/context/ModuleExport.java b/syntax/src/main/java/org/aya/syntax/context/ModuleExport.java index 907fc01c71..12ac7aea8c 100644 --- a/syntax/src/main/java/org/aya/syntax/context/ModuleExport.java +++ b/syntax/src/main/java/org/aya/syntax/context/ModuleExport.java @@ -10,6 +10,7 @@ import org.aya.syntax.concrete.stmt.QualifiedID; import org.aya.syntax.concrete.stmt.UseHide; import org.aya.syntax.ref.AnyDefVar; +import org.aya.syntax.ref.QPath; import org.aya.util.position.WithPos; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -21,13 +22,14 @@ /// ModuleExport stores symbols that imports from another module. /// Any module should NOT export ambiguous symbol/module, they should be solved before they are exported. public record ModuleExport( + @NotNull QPath qualifiedPath, @NotNull MutableMap symbols, @NotNull MutableMap modules ) { - public ModuleExport() { this(MutableMap.create(), MutableMap.create()); } + public ModuleExport(@NotNull QPath qualifiedPath) { this(qualifiedPath, MutableMap.create(), MutableMap.create()); } public ModuleExport(@NotNull ModuleExport that) { - this(MutableMap.from(that.symbols), MutableMap.from(that.modules)); + this(that.qualifiedPath, MutableMap.from(that.symbols), MutableMap.from(that.modules)); } /// @implSpec In case of qualified renaming, only the module is renamed, for example (pseudocode): @@ -50,7 +52,7 @@ public ModuleExport(@NotNull ModuleExport that) { switch (strategy) { case Using -> { - newModule = new ModuleExport(); + newModule = new ModuleExport(qualifiedPath); for (var name : names) { var unit = get(name.component(), name.name()); diff --git a/syntax/src/main/java/org/aya/syntax/ref/ModulePath.java b/syntax/src/main/java/org/aya/syntax/ref/ModulePath.java index a447592a1f..4a3f15c028 100644 --- a/syntax/src/main/java/org/aya/syntax/ref/ModulePath.java +++ b/syntax/src/main/java/org/aya/syntax/ref/ModulePath.java @@ -3,9 +3,13 @@ package org.aya.syntax.ref; import kala.collection.immutable.ImmutableSeq; +import org.aya.generic.AyaDocile; +import org.aya.pretty.doc.Doc; import org.aya.syntax.concrete.stmt.ModuleName; import org.aya.syntax.concrete.stmt.QualifiedID; +import org.aya.util.PrettierOptions; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.Serializable; @@ -25,6 +29,19 @@ public boolean isInModule(@NotNull ModulePath other) { public @NotNull ModulePath derive(@NotNull ModulePath modName) { return new ModulePath(module.concat(modName.module)); } + + /// Remove prefix {@param prefix} and return the remaining components as [ModuleName] + /// + /// @return null if prefix doesn't match, [ModuleName#This] if `this == prefix` + public @Nullable ModuleName removePrefix(@NotNull ModulePath prefix) { + var prefixName = prefix.module; + if (module.sizeLessThan(prefixName.size())) return null; + var prefixMatches = module.sliceView(0, prefixName.size()).sameElements(prefixName); + if (!prefixMatches) return null; + + return ModuleName.from(module.sliceView(prefixName.size(), module.size())); + } + @Override public @NotNull String toString() { return QualifiedID.join(module); } public boolean isEmpty() { return module.isEmpty(); } public int size() { return module.size(); } diff --git a/syntax/src/main/java/org/aya/syntax/ref/QName.java b/syntax/src/main/java/org/aya/syntax/ref/QName.java index 5c82df0946..874ca74766 100644 --- a/syntax/src/main/java/org/aya/syntax/ref/QName.java +++ b/syntax/src/main/java/org/aya/syntax/ref/QName.java @@ -1,8 +1,12 @@ -// Copyright (c) 2020-2024 Tesla (Yinsen) Zhang. +// Copyright (c) 2020-2025 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.syntax.ref; import kala.collection.immutable.ImmutableSeq; +import org.aya.generic.AyaDocile; +import org.aya.pretty.doc.Doc; +import org.aya.syntax.concrete.stmt.QualifiedID; +import org.aya.util.PrettierOptions; import org.jetbrains.annotations.NotNull; import java.io.Serializable; @@ -20,4 +24,9 @@ public QName(@NotNull DefVar ref) { public ImmutableSeq asStringSeq() { return module.module().module().appended(name); } + + @Override + public @NotNull String toString() { + return QualifiedID.join(module.module().module().appended(name)); + } } diff --git a/syntax/src/main/java/org/aya/syntax/ref/QPath.java b/syntax/src/main/java/org/aya/syntax/ref/QPath.java index 1c6668f3c2..a17555dd53 100644 --- a/syntax/src/main/java/org/aya/syntax/ref/QPath.java +++ b/syntax/src/main/java/org/aya/syntax/ref/QPath.java @@ -36,6 +36,14 @@ public record QPath(@NotNull ModulePath module, int fileModuleSize) implements S return new QPath(module.derive(name), fileModuleSize); } + public @NotNull QPath derive(@NotNull ModulePath name) { + return new QPath(module.derive(name), fileModuleSize); + } + + public @NotNull QPath derive(@NotNull ModuleName.Qualified name) { + return new QPath(module.derive(new ModulePath(name.ids())), fileModuleSize); + } + public boolean isFileModule() { return module.size() == fileModuleSize; } @@ -52,4 +60,9 @@ public boolean isFileModule() { return result.toSeq(); } + + @Override + public @NotNull String toString() { + return module.toString(); + } }