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