From d2287a0a580e4d02933eb453e12fbd6ee0d706c7 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Thu, 5 Mar 2026 21:09:20 +0000 Subject: [PATCH] implements PR020 --- .../compiler/pbs/PbsFrontendCompiler.java | 11 +- .../services/PBSFrontendPhaseService.java | 49 +++++++- .../compiler/pbs/PbsFrontendCompilerTest.java | 39 ++++++- .../services/PBSFrontendPhaseServiceTest.java | 105 +++++++++++++++++- 4 files changed, 198 insertions(+), 6 deletions(-) diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java index 19f50685..fcabe4e8 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java @@ -21,17 +21,26 @@ public final class PbsFrontendCompiler { final FileId fileId, final String source, final DiagnosticSink diagnostics) { + final var admissionBaseline = diagnostics.errorCount(); final var tokens = PbsLexer.lex(source, fileId, diagnostics); final var ast = PbsParser.parse(tokens, fileId, diagnostics); - return compileParsedFile(fileId, ast, diagnostics); + final var irBackendFile = compileParsedFile(fileId, ast, diagnostics); + if (diagnostics.errorCount() > admissionBaseline) { + return IRBackendFile.empty(fileId); + } + return irBackendFile; } public IRBackendFile compileParsedFile( final FileId fileId, final PbsAst.File ast, final DiagnosticSink diagnostics) { + final var semanticsErrorBaseline = diagnostics.errorCount(); declarationSemanticsValidator.validate(ast, diagnostics); flowSemanticsValidator.validate(ast, diagnostics); + if (diagnostics.errorCount() > semanticsErrorBaseline) { + return IRBackendFile.empty(fileId); + } return new IRBackendFile(fileId, lowerFunctions(fileId, ast)); } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java index dd04c491..ad0084c9 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java @@ -15,6 +15,7 @@ import p.studio.compiler.pbs.parser.PbsBarrelParser; import p.studio.compiler.pbs.parser.PbsParser; 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.utilities.structures.ReadOnlyList; import p.studio.utilities.logs.LogAggregator; @@ -22,8 +23,11 @@ import p.studio.utilities.logs.LogAggregator; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; @Slf4j public class PBSFrontendPhaseService implements FrontendPhaseService { @@ -39,6 +43,8 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { final var irBackendAggregator = IRBackend.aggregator(); final var parsedSourceFiles = new ArrayList(); final Map modulesByCoordinates = new LinkedHashMap<>(); + final var moduleKeyByFile = new HashMap(); + final var failedModuleKeys = new HashSet(); for (final var pId : ctx.stack.reverseTopologicalOrder) { final var projectDescriptor = ctx.projectTable.get(pId); @@ -49,24 +55,36 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { sourceHandle.readUtf8().ifPresentOrElse( utf8Content -> { final var coordinates = resolveModuleCoordinates(projectDescriptor, sourceHandle); + final var moduleKey = moduleKey(coordinates); final var moduleUnit = modulesByCoordinates.computeIfAbsent( coordinates, ignored -> new MutableModuleUnit()); switch (sourceHandle.getExtension()) { case "pbs" -> { + moduleKeyByFile.put(fId, moduleKey); + final var parseErrorBaseline = diagnostics.errorCount(); final var ast = parseSourceFile(fId, utf8Content, diagnostics); moduleUnit.sources.add(new PbsModuleVisibilityValidator.SourceFile(fId, ast)); - parsedSourceFiles.add(new ParsedSourceFile(fId, ast)); + parsedSourceFiles.add(new ParsedSourceFile(fId, ast, moduleKey)); + if (diagnostics.errorCount() > parseErrorBaseline) { + failedModuleKeys.add(moduleKey); + } } case "barrel" -> { + moduleKeyByFile.put(fId, moduleKey); 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); + } } 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); } } default -> { @@ -89,8 +107,12 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { ReadOnlyList.wrap(moduleUnit.barrels))); } moduleVisibilityValidator.validate(ReadOnlyList.wrap(modules), diagnostics); + markModulesWithLinkingErrors(diagnostics, moduleKeyByFile, failedModuleKeys); for (final var parsedSource : parsedSourceFiles) { + if (failedModuleKeys.contains(parsedSource.moduleKey())) { + continue; + } final var irBackendFile = frontendCompiler.compileParsedFile(parsedSource.fileId(), parsedSource.ast(), diagnostics); irBackendAggregator.merge(irBackendFile); } @@ -145,6 +167,28 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { return sourceHandle.getRelativePath(); } + private void markModulesWithLinkingErrors( + final DiagnosticSink diagnostics, + final Map moduleKeyByFile, + final Set failedModuleKeys) { + for (final var diagnostic : diagnostics) { + if (!diagnostic.getSeverity().isError()) { + continue; + } + if (diagnostic.getPhase() != DiagnosticPhase.LINKING) { + continue; + } + final var moduleKey = moduleKeyByFile.get(diagnostic.getSpan().getFileId()); + if (moduleKey != null) { + failedModuleKeys.add(moduleKey); + } + } + } + + private String moduleKey(final PbsModuleVisibilityValidator.ModuleCoordinates coordinates) { + return coordinates.project() + ":" + String.join("/", coordinates.pathSegments().asList()); + } + private static final class MutableModuleUnit { private final ArrayList sources = new ArrayList<>(); private final ArrayList barrels = new ArrayList<>(); @@ -152,6 +196,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { private record ParsedSourceFile( FileId fileId, - PbsAst.File ast) { + PbsAst.File ast, + String moduleKey) { } } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java index d29a7183..04d86742 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java @@ -1,6 +1,7 @@ package p.studio.compiler.pbs; import org.junit.jupiter.api.Test; +import p.studio.compiler.pbs.lexer.LexErrors; import p.studio.compiler.pbs.semantics.PbsSemanticsErrors; import p.studio.compiler.source.diagnostics.DiagnosticSink; import p.studio.compiler.source.identifiers.FileId; @@ -44,10 +45,11 @@ class PbsFrontendCompilerTest { final var diagnostics = DiagnosticSink.empty(); final var compiler = new PbsFrontendCompiler(); - compiler.compileFile(new FileId(0), source, diagnostics); + final var fileBackend = compiler.compileFile(new FileId(0), source, diagnostics); assertTrue(diagnostics.stream().anyMatch(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_CALLABLE_SHAPE.name()))); + assertEquals(0, fileBackend.functions().size()); } @Test @@ -59,9 +61,42 @@ class PbsFrontendCompilerTest { final var diagnostics = DiagnosticSink.empty(); final var compiler = new PbsFrontendCompiler(); - compiler.compileFile(new FileId(0), source, diagnostics); + final var fileBackend = compiler.compileFile(new FileId(0), source, diagnostics); assertTrue(diagnostics.stream().noneMatch(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_CALLABLE_SHAPE.name()))); + assertEquals(2, fileBackend.functions().size()); + } + + @Test + void shouldNotLowerWhenSyntaxErrorsExist() { + final var source = """ + $ + fn run() -> int { return 1; } + """; + + final var diagnostics = DiagnosticSink.empty(); + final var compiler = new PbsFrontendCompiler(); + final var fileBackend = compiler.compileFile(new FileId(0), source, diagnostics); + + assertTrue(diagnostics.stream().anyMatch(d -> + d.getCode().equals(LexErrors.E_LEX_INVALID_CHAR.name()))); + assertEquals(0, fileBackend.functions().size()); + } + + @Test + void shouldNotLowerWhenStaticSemanticsErrorsExist() { + final var source = """ + fn run(x: int) -> int { return x; } + fn run(y: int) -> int { return y; } + """; + + final var diagnostics = DiagnosticSink.empty(); + final var compiler = new PbsFrontendCompiler(); + final var fileBackend = compiler.compileFile(new FileId(0), source, diagnostics); + + assertTrue(diagnostics.stream().anyMatch(d -> + d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_CALLABLE_SHAPE.name()))); + assertEquals(0, fileBackend.functions().size()); } } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java index 98618659..c7368dc7 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java @@ -21,6 +21,7 @@ import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.List; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; class PBSFrontendPhaseServiceTest { @@ -61,13 +62,115 @@ class PBSFrontendPhaseServiceTest { new BuildStack(ReadOnlyList.wrap(List.of(projectId)))); final var diagnostics = DiagnosticSink.empty(); - new PBSFrontendPhaseService().compile( + final var irBackend = new PBSFrontendPhaseService().compile( ctx, diagnostics, LogAggregator.empty(), BuildingIssueSink.empty()); assertTrue(diagnostics.stream().anyMatch(d -> d.getCode().equals(PbsLinkErrors.E_LINK_INVALID_BARREL_FILENAME.name()))); + assertEquals(0, irBackend.getFunctions().size()); + } + + @Test + void shouldLowerOnlyModulesThatPassAdmissionGates() throws IOException { + final var projectRoot = tempDir.resolve("project-mixed"); + final var sourceRoot = projectRoot.resolve("src"); + final var validModulePath = sourceRoot.resolve("valid"); + final var invalidModulePath = sourceRoot.resolve("invalid"); + Files.createDirectories(validModulePath); + Files.createDirectories(invalidModulePath); + + final var validSource = validModulePath.resolve("source.pbs"); + final var validBarrel = validModulePath.resolve("mod.barrel"); + final var invalidSource = invalidModulePath.resolve("source.pbs"); + Files.writeString(validSource, """ + fn good(v: int) -> int { + return v; + } + """); + Files.writeString(validBarrel, "pub fn good(v: int) -> int;"); + Files.writeString(invalidSource, """ + fn bad(v: int) -> int { + return v; + } + """); + + final var projectTable = new ProjectTable(); + final var fileTable = new FileTable(1); + final var projectId = projectTable.register(ProjectDescriptor.builder() + .rootPath(projectRoot) + .name("core") + .version("1.0.0") + .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot))) + .build()); + + registerFile(projectId, projectRoot, validSource, fileTable); + registerFile(projectId, projectRoot, validBarrel, fileTable); + registerFile(projectId, projectRoot, invalidSource, fileTable); + + final var ctx = new FrontendPhaseContext( + projectTable, + fileTable, + new BuildStack(ReadOnlyList.wrap(List.of(projectId)))); + final var diagnostics = DiagnosticSink.empty(); + + final var irBackend = new PBSFrontendPhaseService().compile( + ctx, + diagnostics, + LogAggregator.empty(), + BuildingIssueSink.empty()); + + assertTrue(diagnostics.stream().anyMatch(d -> + d.getCode().equals(PbsLinkErrors.E_LINK_MISSING_BARREL.name()))); + assertEquals(1, irBackend.getFunctions().size()); + assertEquals("good", irBackend.getFunctions().getFirst().name()); + } + + @Test + void shouldLowerAllSourcesWhenNoAdmissionErrorsExist() throws IOException { + final var projectRoot = tempDir.resolve("project-valid"); + final var sourceRoot = projectRoot.resolve("src"); + final var modulePath = sourceRoot.resolve("math"); + Files.createDirectories(modulePath); + + final var sourceFile = modulePath.resolve("source.pbs"); + final var modBarrel = modulePath.resolve("mod.barrel"); + Files.writeString(sourceFile, """ + fn run() -> int { return 1; } + fn sum(a: int, b: int) -> int { return a + b; } + """); + Files.writeString(modBarrel, """ + pub fn run() -> int; + pub fn sum(a: int, b: int) -> int; + """); + + final var projectTable = new ProjectTable(); + final var fileTable = new FileTable(1); + final var projectId = projectTable.register(ProjectDescriptor.builder() + .rootPath(projectRoot) + .name("core") + .version("1.0.0") + .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot))) + .build()); + + registerFile(projectId, projectRoot, sourceFile, fileTable); + registerFile(projectId, projectRoot, modBarrel, fileTable); + + final var ctx = new FrontendPhaseContext( + projectTable, + fileTable, + new BuildStack(ReadOnlyList.wrap(List.of(projectId)))); + final var diagnostics = DiagnosticSink.empty(); + + final var irBackend = new PBSFrontendPhaseService().compile( + ctx, + diagnostics, + LogAggregator.empty(), + BuildingIssueSink.empty()); + + assertTrue(diagnostics.isEmpty()); + assertEquals(2, irBackend.getFunctions().size()); } private void registerFile(