implements PR-05.0.2

This commit is contained in:
bQUARKz 2026-03-09 06:18:45 +00:00
parent 3b5e8e0b24
commit bafab68eac
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
4 changed files with 123 additions and 48 deletions

View File

@ -21,6 +21,9 @@ import p.studio.compiler.source.Span;
import p.studio.compiler.source.diagnostics.DiagnosticSink; import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.diagnostics.DiagnosticPhase; import p.studio.compiler.source.diagnostics.DiagnosticPhase;
import p.studio.compiler.source.identifiers.FileId; import p.studio.compiler.source.identifiers.FileId;
import p.studio.compiler.source.identifiers.ModuleId;
import p.studio.compiler.source.tables.ModuleReference;
import p.studio.compiler.source.tables.ModuleTable;
import p.studio.utilities.structures.ReadOnlyList; import p.studio.utilities.structures.ReadOnlyList;
import p.studio.utilities.logs.LogAggregator; import p.studio.utilities.logs.LogAggregator;
@ -62,8 +65,9 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
final var irBackendAggregator = IRBackend.aggregator(); final var irBackendAggregator = IRBackend.aggregator();
final var parsedSourceFiles = new ArrayList<ParsedSourceFile>(); final var parsedSourceFiles = new ArrayList<ParsedSourceFile>();
final Map<PbsModuleVisibilityValidator.ModuleCoordinates, MutableModuleUnit> modulesByCoordinates = new LinkedHashMap<>(); final Map<PbsModuleVisibilityValidator.ModuleCoordinates, MutableModuleUnit> modulesByCoordinates = new LinkedHashMap<>();
final var moduleKeyByFile = new HashMap<FileId, String>(); final var moduleTable = new ModuleTable();
final var failedModuleKeys = new HashSet<String>(); final var moduleIdByFile = new HashMap<FileId, ModuleId>();
final var failedModuleIds = new HashSet<ModuleId>();
for (final var pId : ctx.stack.reverseTopologicalOrder) { for (final var pId : ctx.stack.reverseTopologicalOrder) {
final var projectDescriptor = ctx.projectTable.get(pId); final var projectDescriptor = ctx.projectTable.get(pId);
@ -75,36 +79,36 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
sourceHandle.readUtf8().ifPresentOrElse( sourceHandle.readUtf8().ifPresentOrElse(
utf8Content -> { utf8Content -> {
final var coordinates = resolveModuleCoordinates(projectDescriptor, sourceHandle); final var coordinates = resolveModuleCoordinates(projectDescriptor, sourceHandle);
final var moduleKey = moduleKey(coordinates); final var moduleId = moduleId(moduleTable, coordinates);
final var moduleUnit = modulesByCoordinates.computeIfAbsent( final var moduleUnit = modulesByCoordinates.computeIfAbsent(
coordinates, coordinates,
ignored -> new MutableModuleUnit()); ignored -> new MutableModuleUnit());
switch (sourceHandle.getExtension()) { switch (sourceHandle.getExtension()) {
case "pbs" -> { case "pbs" -> {
moduleKeyByFile.put(fId, moduleKey); moduleIdByFile.put(fId, moduleId);
final var parseErrorBaseline = diagnostics.errorCount(); final var parseErrorBaseline = diagnostics.errorCount();
final var ast = parseSourceFile(fId, utf8Content, diagnostics, projectSourceKind); final var ast = parseSourceFile(fId, utf8Content, diagnostics, projectSourceKind);
moduleUnit.sources.add(new PbsModuleVisibilityValidator.SourceFile(fId, ast)); moduleUnit.sources.add(new PbsModuleVisibilityValidator.SourceFile(fId, ast));
parsedSourceFiles.add(new ParsedSourceFile(fId, ast, moduleKey, projectSourceKind)); parsedSourceFiles.add(new ParsedSourceFile(fId, ast, moduleId, projectSourceKind));
if (diagnostics.errorCount() > parseErrorBaseline) { if (diagnostics.errorCount() > parseErrorBaseline) {
failedModuleKeys.add(moduleKey); failedModuleIds.add(moduleId);
} }
} }
case "barrel" -> { case "barrel" -> {
moduleKeyByFile.put(fId, moduleKey); moduleIdByFile.put(fId, moduleId);
if ("mod.barrel".equals(sourceHandle.getFilename())) { if ("mod.barrel".equals(sourceHandle.getFilename())) {
final var parseErrorBaseline = diagnostics.errorCount(); final var parseErrorBaseline = diagnostics.errorCount();
final var barrelAst = parseBarrelFile(fId, utf8Content, diagnostics); final var barrelAst = parseBarrelFile(fId, utf8Content, diagnostics);
moduleUnit.barrels.add(new PbsModuleVisibilityValidator.BarrelFile(fId, barrelAst)); moduleUnit.barrels.add(new PbsModuleVisibilityValidator.BarrelFile(fId, barrelAst));
if (diagnostics.errorCount() > parseErrorBaseline) { if (diagnostics.errorCount() > parseErrorBaseline) {
failedModuleKeys.add(moduleKey); failedModuleIds.add(moduleId);
} }
} else { } else {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
PbsLinkErrors.E_LINK_INVALID_BARREL_FILENAME.name(), PbsLinkErrors.E_LINK_INVALID_BARREL_FILENAME.name(),
"Only 'mod.barrel' is allowed as barrel filename", "Only 'mod.barrel' is allowed as barrel filename",
new Span(fId, 0, utf8Content.getBytes(StandardCharsets.UTF_8).length)); new Span(fId, 0, utf8Content.getBytes(StandardCharsets.UTF_8).length));
failedModuleKeys.add(moduleKey); failedModuleIds.add(moduleId);
} }
} }
default -> { default -> {
@ -120,7 +124,8 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
loadReservedStdlibModules( loadReservedStdlibModules(
modulesByCoordinates, modulesByCoordinates,
parsedSourceFiles, parsedSourceFiles,
moduleKeyByFile, moduleIdByFile,
moduleTable,
diagnostics, diagnostics,
ctx.stdlibVersion()); ctx.stdlibVersion());
@ -134,13 +139,13 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
ReadOnlyList.wrap(moduleUnit.barrels))); ReadOnlyList.wrap(moduleUnit.barrels)));
} }
moduleVisibilityValidator.validate(ReadOnlyList.wrap(modules), nameTable, diagnostics); moduleVisibilityValidator.validate(ReadOnlyList.wrap(modules), nameTable, diagnostics);
markModulesWithLinkingErrors(diagnostics, moduleKeyByFile, failedModuleKeys); markModulesWithLinkingErrors(diagnostics, moduleIdByFile, failedModuleIds);
final var moduleDependencyGraph = buildModuleDependencyGraph(parsedSourceFiles); final var moduleDependencyGraph = buildModuleDependencyGraph(parsedSourceFiles, moduleTable);
final var compiledSourceFiles = new ArrayList<CompiledSourceFile>(parsedSourceFiles.size()); final var compiledSourceFiles = new ArrayList<CompiledSourceFile>(parsedSourceFiles.size());
for (final var parsedSource : parsedSourceFiles) { for (final var parsedSource : parsedSourceFiles) {
final var blockedModuleKeys = blockedModulesByDependency(failedModuleKeys, moduleDependencyGraph); final var blockedModuleIds = blockedModulesByDependency(failedModuleIds, moduleDependencyGraph);
if (blockedModuleKeys.contains(parsedSource.moduleKey())) { if (blockedModuleIds.contains(parsedSource.moduleId())) {
continue; continue;
} }
final var compileErrorBaseline = diagnostics.errorCount(); final var compileErrorBaseline = diagnostics.errorCount();
@ -149,18 +154,18 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
parsedSource.ast(), parsedSource.ast(),
diagnostics, diagnostics,
parsedSource.sourceKind(), parsedSource.sourceKind(),
parsedSource.moduleKey(), renderModuleKey(moduleTable, parsedSource.moduleId()),
ctx.hostAdmissionContext(), ctx.hostAdmissionContext(),
nameTable); nameTable);
if (diagnostics.errorCount() > compileErrorBaseline) { if (diagnostics.errorCount() > compileErrorBaseline) {
failedModuleKeys.add(parsedSource.moduleKey()); failedModuleIds.add(parsedSource.moduleId());
} }
compiledSourceFiles.add(new CompiledSourceFile(parsedSource.moduleKey(), irBackendFile)); compiledSourceFiles.add(new CompiledSourceFile(parsedSource.moduleId(), irBackendFile));
} }
final var blockedModuleKeys = blockedModulesByDependency(failedModuleKeys, moduleDependencyGraph); final var blockedModuleIds = blockedModulesByDependency(failedModuleIds, moduleDependencyGraph);
for (final var compiledSource : compiledSourceFiles) { for (final var compiledSource : compiledSourceFiles) {
if (blockedModuleKeys.contains(compiledSource.moduleKey())) { if (blockedModuleIds.contains(compiledSource.moduleId())) {
continue; continue;
} }
irBackendAggregator.merge(compiledSource.irBackendFile()); irBackendAggregator.merge(compiledSource.irBackendFile());
@ -174,18 +179,19 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
private void loadReservedStdlibModules( private void loadReservedStdlibModules(
final Map<PbsModuleVisibilityValidator.ModuleCoordinates, MutableModuleUnit> modulesByCoordinates, final Map<PbsModuleVisibilityValidator.ModuleCoordinates, MutableModuleUnit> modulesByCoordinates,
final ArrayList<ParsedSourceFile> parsedSourceFiles, final ArrayList<ParsedSourceFile> parsedSourceFiles,
final Map<FileId, String> moduleKeyByFile, final Map<FileId, ModuleId> moduleIdByFile,
final ModuleTable moduleTable,
final DiagnosticSink diagnostics, final DiagnosticSink diagnostics,
final int stdlibVersion) { final int stdlibVersion) {
final var stdlibEnvironment = stdlibEnvironmentResolver.resolve(stdlibVersion); final var stdlibEnvironment = stdlibEnvironmentResolver.resolve(stdlibVersion);
final var pending = new ArrayDeque<PbsModuleVisibilityValidator.ModuleCoordinates>(); final var pending = new ArrayDeque<PbsModuleVisibilityValidator.ModuleCoordinates>();
final var resolved = new HashSet<String>(); final var resolved = new HashSet<ModuleId>();
enqueueReservedImportsFromKnownModules(modulesByCoordinates, pending); enqueueReservedImportsFromKnownModules(modulesByCoordinates, pending);
while (!pending.isEmpty()) { while (!pending.isEmpty()) {
final var target = pending.removeFirst(); final var target = pending.removeFirst();
final var targetKey = moduleKey(target); final var targetId = moduleId(moduleTable, target);
if (!resolved.add(targetKey)) { if (!resolved.add(targetId)) {
continue; continue;
} }
if (modulesByCoordinates.containsKey(target)) { if (modulesByCoordinates.containsKey(target)) {
@ -203,15 +209,15 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
moduleData.barrels.addAll(loadedModule.barrelFiles().asList()); moduleData.barrels.addAll(loadedModule.barrelFiles().asList());
modulesByCoordinates.put(loadedModule.coordinates(), moduleData); modulesByCoordinates.put(loadedModule.coordinates(), moduleData);
for (final var sourceFile : loadedModule.sourceFiles()) { for (final var sourceFile : loadedModule.sourceFiles()) {
moduleKeyByFile.put(sourceFile.fileId(), targetKey); moduleIdByFile.put(sourceFile.fileId(), targetId);
parsedSourceFiles.add(new ParsedSourceFile( parsedSourceFiles.add(new ParsedSourceFile(
sourceFile.fileId(), sourceFile.fileId(),
sourceFile.ast(), sourceFile.ast(),
targetKey, targetId,
SourceKind.SDK_INTERFACE)); SourceKind.SDK_INTERFACE));
} }
for (final var barrelFile : loadedModule.barrelFiles()) { for (final var barrelFile : loadedModule.barrelFiles()) {
moduleKeyByFile.put(barrelFile.fileId(), targetKey); moduleIdByFile.put(barrelFile.fileId(), targetId);
} }
enqueueReservedImportsFromSourceFiles(loadedModule.sourceFiles().asList(), pending); enqueueReservedImportsFromSourceFiles(loadedModule.sourceFiles().asList(), pending);
} }
@ -296,8 +302,8 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
private void markModulesWithLinkingErrors( private void markModulesWithLinkingErrors(
final DiagnosticSink diagnostics, final DiagnosticSink diagnostics,
final Map<FileId, String> moduleKeyByFile, final Map<FileId, ModuleId> moduleIdByFile,
final Set<String> failedModuleKeys) { final Set<ModuleId> failedModuleIds) {
for (final var diagnostic : diagnostics) { for (final var diagnostic : diagnostics) {
if (!diagnostic.getSeverity().isError()) { if (!diagnostic.getSeverity().isError()) {
continue; continue;
@ -305,32 +311,33 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
if (diagnostic.getPhase() != DiagnosticPhase.LINKING) { if (diagnostic.getPhase() != DiagnosticPhase.LINKING) {
continue; continue;
} }
final var moduleKey = moduleKeyByFile.get(diagnostic.getSpan().getFileId()); final var moduleId = moduleIdByFile.get(diagnostic.getSpan().getFileId());
if (moduleKey != null) { if (moduleId != null) {
failedModuleKeys.add(moduleKey); failedModuleIds.add(moduleId);
} }
} }
} }
private Map<String, Set<String>> buildModuleDependencyGraph( private Map<ModuleId, Set<ModuleId>> buildModuleDependencyGraph(
final ArrayList<ParsedSourceFile> parsedSourceFiles) { final ArrayList<ParsedSourceFile> parsedSourceFiles,
final Map<String, Set<String>> dependenciesByModule = new HashMap<>(); final ModuleTable moduleTable) {
final Map<ModuleId, Set<ModuleId>> dependenciesByModule = new HashMap<>();
for (final var parsedSource : parsedSourceFiles) { for (final var parsedSource : parsedSourceFiles) {
final var moduleDependencies = dependenciesByModule.computeIfAbsent( final var moduleDependencies = dependenciesByModule.computeIfAbsent(
parsedSource.moduleKey(), parsedSource.moduleId(),
ignored -> new HashSet<>()); ignored -> new HashSet<>());
for (final var importDecl : parsedSource.ast().imports()) { for (final var importDecl : parsedSource.ast().imports()) {
final var moduleRef = importDecl.moduleRef(); final var moduleRef = importDecl.moduleRef();
moduleDependencies.add(moduleKey(moduleRef.project(), moduleRef.pathSegments())); moduleDependencies.add(moduleId(moduleTable, moduleRef.project(), moduleRef.pathSegments()));
} }
} }
return dependenciesByModule; return dependenciesByModule;
} }
private Set<String> blockedModulesByDependency( private Set<ModuleId> blockedModulesByDependency(
final Set<String> failedModuleKeys, final Set<ModuleId> failedModuleIds,
final Map<String, Set<String>> dependenciesByModule) { final Map<ModuleId, Set<ModuleId>> dependenciesByModule) {
final Map<String, Set<String>> dependentsByModule = new HashMap<>(); final Map<ModuleId, Set<ModuleId>> dependentsByModule = new HashMap<>();
for (final var entry : dependenciesByModule.entrySet()) { for (final var entry : dependenciesByModule.entrySet()) {
final var importer = entry.getKey(); final var importer = entry.getKey();
for (final var dependency : entry.getValue()) { for (final var dependency : entry.getValue()) {
@ -338,8 +345,8 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
} }
} }
final var blocked = new HashSet<String>(failedModuleKeys); final var blocked = new HashSet<ModuleId>(failedModuleIds);
final var pending = new ArrayDeque<String>(failedModuleKeys); final var pending = new ArrayDeque<ModuleId>(failedModuleIds);
while (!pending.isEmpty()) { while (!pending.isEmpty()) {
final var failedOrBlocked = pending.removeFirst(); final var failedOrBlocked = pending.removeFirst();
final var dependents = dependentsByModule.getOrDefault(failedOrBlocked, Set.of()); final var dependents = dependentsByModule.getOrDefault(failedOrBlocked, Set.of());
@ -353,14 +360,24 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
return blocked; return blocked;
} }
private String moduleKey(final PbsModuleVisibilityValidator.ModuleCoordinates coordinates) { private ModuleId moduleId(
return coordinates.project() + ":" + String.join("/", coordinates.pathSegments().asList()); final ModuleTable moduleTable,
final PbsModuleVisibilityValidator.ModuleCoordinates coordinates) {
return moduleId(moduleTable, coordinates.project(), coordinates.pathSegments());
} }
private String moduleKey( private ModuleId moduleId(
final ModuleTable moduleTable,
final String project, final String project,
final ReadOnlyList<String> pathSegments) { final ReadOnlyList<String> pathSegments) {
return project + ":" + String.join("/", pathSegments.asList()); return moduleTable.register(new ModuleReference(project, pathSegments));
}
private String renderModuleKey(
final ModuleTable moduleTable,
final ModuleId moduleId) {
final var moduleReference = moduleTable.get(moduleId);
return moduleReference.project() + ":" + String.join("/", moduleReference.pathSegments().asList());
} }
private static final class MutableModuleUnit { private static final class MutableModuleUnit {
@ -371,12 +388,12 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
private record ParsedSourceFile( private record ParsedSourceFile(
FileId fileId, FileId fileId,
PbsAst.File ast, PbsAst.File ast,
String moduleKey, ModuleId moduleId,
SourceKind sourceKind) { SourceKind sourceKind) {
} }
private record CompiledSourceFile( private record CompiledSourceFile(
String moduleKey, ModuleId moduleId,
p.studio.compiler.models.IRBackendFile irBackendFile) { p.studio.compiler.models.IRBackendFile irBackendFile) {
} }
} }

View File

@ -0,0 +1,17 @@
package p.studio.compiler.source.tables;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.Objects;
public record ModuleReference(
String project,
ReadOnlyList<String> pathSegments) {
public ModuleReference {
project = Objects.requireNonNull(project, "project");
if (project.isBlank()) {
throw new IllegalArgumentException("project must not be blank");
}
pathSegments = pathSegments == null ? ReadOnlyList.empty() : pathSegments;
}
}

View File

@ -0,0 +1,10 @@
package p.studio.compiler.source.tables;
import p.studio.compiler.source.identifiers.ModuleId;
public class ModuleTable extends InternTable<ModuleId, ModuleReference> {
public ModuleTable() {
super(ModuleId::new);
}
}

View File

@ -0,0 +1,31 @@
package p.studio.compiler.source.tables;
import org.junit.jupiter.api.Test;
import p.studio.utilities.structures.ReadOnlyList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
class ModuleTableTest {
@Test
void shouldInternSameReferenceWithStableId() {
final var table = new ModuleTable();
final var reference = new ModuleReference("core", ReadOnlyList.wrap(java.util.List.of("math", "vec")));
final var first = table.register(reference);
final var second = table.register(reference);
assertEquals(first, second);
}
@Test
void shouldAllocateDifferentIdForDifferentReference() {
final var table = new ModuleTable();
final var first = table.register(new ModuleReference("core", ReadOnlyList.wrap(java.util.List.of("math"))));
final var second = table.register(new ModuleReference("core", ReadOnlyList.wrap(java.util.List.of("gfx"))));
assertNotEquals(first, second);
}
}