diff --git a/docs/pbs/pull-requests/PR-009-pbs-diagnostics-contract-v1.md b/docs/pbs/pull-requests/PR-009-pbs-diagnostics-contract-v1.md deleted file mode 100644 index 3ab3a7b2..00000000 --- a/docs/pbs/pull-requests/PR-009-pbs-diagnostics-contract-v1.md +++ /dev/null @@ -1,36 +0,0 @@ -# PR-009 - PBS Diagnostics Contract V1 - -## Briefing -O modelo atual de `Diagnostic` nao contem fase, template id nem placeholders estaveis, o que impede conformidade com o contrato de diagnosticos da spec. -Este PR eleva o contrato de diagnosticos para v1 sem acoplar a UI. - -## Target -- Specs: - - `docs/pbs/specs/12. Diagnostics Specification.md` (secoes 6, 7, 8, 9, 10) - - `docs/pbs/specs/13. Lowering IRBackend Specification.md` (secao 7) -- Codigo: - - `prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/Diagnostic.java` - - `prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/DiagnosticSink.java` - - emissores de diagnostico no frontend PBS. - -## Method -1. Estender `Diagnostic` com `phase`, `templateId` e `placeholders` nomeados. -2. Definir taxonomia minima de fases externas: `syntax`, `static-semantics`, `manifest-import-resolution`, `linking`, `host-admission`, `load-facing-rejection`. -3. Preservar `code`, `severity`, `span` e `related spans` como identidade estavel. -4. Introduzir helpers para emissao padronizada por fase/template. -5. Mapear erros atuais de lexer/parser/frontend para o novo contrato. - -## Acceptance Criteria -- Todo diagnostico PBS emitido tem `code`, `severity`, `phase`, `templateId`, `primary span` e mensagem renderizada. -- Duplicatas/conflitos incluem ao menos um `related span` quando aplicavel. -- Identidade de diagnostico independe do texto localizado. -- Nenhuma fase obrigatoria e colapsada de forma opaca para o usuario. -- Chamadas legadas continuam compilando com caminho de migracao claro. - -## Tests -- `DiagnosticSinkTest` e novos testes de contrato para: - - presenca obrigatoria de campos; - - estabilidade de `templateId/placeholders`; - - caso com `related span`; - - serializacao/`toString` com fase e codigo. -- Testes de integracao no frontend PBS garantindo fase correta por erro. diff --git a/docs/pbs/pull-requests/PR-010-pbs-irbackend-lowering-contract.md b/docs/pbs/pull-requests/PR-010-pbs-irbackend-lowering-contract.md deleted file mode 100644 index 73abd5d0..00000000 --- a/docs/pbs/pull-requests/PR-010-pbs-irbackend-lowering-contract.md +++ /dev/null @@ -1,35 +0,0 @@ -# PR-010 - PBS IRBackend Lowering Contract Alignment - -## Briefing -O lowering atual reduz tudo a `IRFunction(name, arity, hasReturnType)` e perde informacao exigida pela spec de lowering. -Este PR alinha o frontend boundary para preservar identidade, superficie de retorno e atribuicao de origem. - -## Target -- Specs: - - `docs/pbs/specs/13. Lowering IRBackend Specification.md` (secoes 5, 6, 7, 8) - - `docs/pbs/specs/11. AST Specification.md` (metadados obrigatorios) -- Codigo: - - `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java` - - `prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRFunction.java` - - `prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackend*.java` - -## Method -1. Expandir modelos de IR para preservar categoria de callable e superficie de retorno. -2. Propagar atribuicao de origem estavel (`file + span`) de forma obrigatoria. -3. Diferenciar rejeicao de forma nao suportada vs erro interno. -4. Garantir determinismo de ordem de emissao conforme ordem de fonte. -5. Manter bridge de compatibilidade para consumidores atuais de IR. - -## Acceptance Criteria -- IRBackend preserva identidade e aridade de callables sem perda de categoria. -- Retorno (unit/plain/result) e mantido no boundary de frontend. -- Diagnosticos de lowering seguem contrato estavel (code/phase/template/span). -- Formas nao suportadas nao degradam silenciosamente para comportamento valido alternativo. -- Testes existentes de lowering continuam passando com adaptacao minima. - -## Tests -- `PbsFrontendCompilerTest` ampliado para: - - preservacao de metadata no IR; - - ordem deterministica de callables; - - casos de rejeicao de lowering com diagnostico estavel. -- Novos testes para serializacao/debug de `IRBackend`. diff --git a/docs/pbs/pull-requests/PR-011-pbs-gate-u-conformance-fixtures.md b/docs/pbs/pull-requests/PR-011-pbs-gate-u-conformance-fixtures.md deleted file mode 100644 index d567971b..00000000 --- a/docs/pbs/pull-requests/PR-011-pbs-gate-u-conformance-fixtures.md +++ /dev/null @@ -1,35 +0,0 @@ -# PR-011 - PBS Gate U Conformance Fixtures - -## Briefing -A spec exige evidencia de conformidade (Gate U), mas o frontend ainda tem poucos testes unitarios sem fixture matrix. -Este PR cria a infraestrutura e a bateria minima de fixtures para validar lexer, parser, semantica, diagnosticos e lowering. - -## Target -- Specs: - - `docs/pbs/specs/11. AST Specification.md` (secao 12) - - `docs/pbs/specs/13. Lowering IRBackend Specification.md` (secao 8) - - `docs/general/specs/13. Conformance Test Specification.md` -- Codigo: - - novo pacote de teste/fixtures em `prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/`. - -## Method -1. Introduzir harness de fixture com entrada `.pbs/.barrel` + expectativa estruturada. -2. Cobrir casos validos das familias obrigatorias de AST e fluxo de parse. -3. Cobrir casos invalidos obrigatorios (missing closer, non-assoc chains, formas fora de slice). -4. Adicionar asserts de spans e identidade de diagnostico (code/phase/template). -5. Integrar suite no gradle test da frontend PBS. - -## Acceptance Criteria -- Existe fixture matrix positiva e negativa para lexer/parser/semantica/lowering. -- Gate U cobre familias obrigatorias de declaracao, statement e expressao. -- Casos de recovery garantem AST coerente mesmo com erro. -- Conformidade e verificavel de forma reproduzivel em CI. -- Falhas mostram diff util entre esperado e obtido. - -## Tests -- `PbsConformanceFixtureTest` novo com subconjuntos: - - `valid/` para parse e lowering; - - `invalid/syntax/`; - - `invalid/static-semantics/`; - - `invalid/lowering/`. -- Execucao via `./gradlew :prometeu-compiler:frontends:prometeu-frontend-pbs:test`. diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lexer/PbsLexer.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lexer/PbsLexer.java index 5a43b656..dc3d5171 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lexer/PbsLexer.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lexer/PbsLexer.java @@ -270,7 +270,7 @@ public final class PbsLexer { final String message, final long spanStart, final long spanEnd) { - diagnostics.error(lexErrors.name(), message, new Span(fileId, spanStart, spanEnd)); + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, lexErrors.name(), message, new Span(fileId, spanStart, spanEnd)); } private static Map buildKeywords() { diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/linking/PbsModuleVisibilityValidator.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/linking/PbsModuleVisibilityValidator.java index b81627b6..d648a1da 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/linking/PbsModuleVisibilityValidator.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/linking/PbsModuleVisibilityValidator.java @@ -43,7 +43,7 @@ public final class PbsModuleVisibilityValidator { if (module.sourceFiles().isEmpty()) { if (!module.barrelFiles().isEmpty()) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsLinkErrors.E_LINK_BARREL_WITHOUT_SOURCE.name(), "Module %s has mod.barrel but no .pbs source files".formatted(displayModule(module.coordinates())), module.barrelFiles().getFirst().ast().span()); @@ -52,7 +52,7 @@ public final class PbsModuleVisibilityValidator { } if (module.barrelFiles().isEmpty()) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsLinkErrors.E_LINK_MISSING_BARREL.name(), "Module %s is missing mod.barrel".formatted(displayModule(module.coordinates())), module.sourceFiles().getFirst().ast().span()); @@ -63,7 +63,7 @@ public final class PbsModuleVisibilityValidator { final var first = module.barrelFiles().getFirst(); for (int i = 1; i < module.barrelFiles().size(); i++) { final var duplicate = module.barrelFiles().get(i); - diagnostics.report( + p.studio.compiler.source.diagnostics.Diagnostics.report(diagnostics, Severity.Error, PbsLinkErrors.E_LINK_DUPLICATE_BARREL_FILE.name(), "Module %s has multiple mod.barrel files".formatted(displayModule(module.coordinates())), @@ -74,8 +74,8 @@ public final class PbsModuleVisibilityValidator { final var barrel = module.barrelFiles().getFirst(); final var declarations = collectDeclarations(module, nameTable); - final Set seenNonFunctionEntries = new HashSet<>(); - final Set seenFunctionEntries = new HashSet<>(); + final Map seenNonFunctionEntries = new HashMap<>(); + final Map seenFunctionEntries = new HashMap<>(); for (final var item : barrel.ast().items()) { if (item instanceof PbsAst.BarrelFunctionItem functionItem) { @@ -86,19 +86,21 @@ public final class PbsModuleVisibilityValidator { functionItem.returnType(), functionItem.resultErrorType(), nameTable); - if (!seenFunctionEntries.add(functionKey)) { - diagnostics.error( + final var firstFunctionEntry = seenFunctionEntries.putIfAbsent(functionKey, functionItem.span()); + if (firstFunctionEntry != null) { + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsLinkErrors.E_LINK_DUPLICATE_BARREL_ENTRY.name(), "Duplicate barrel function entry '%s' in module %s".formatted( functionItem.name(), displayModule(module.coordinates())), - functionItem.span()); + functionItem.span(), + List.of(new RelatedSpan("First barrel function entry is here", firstFunctionEntry))); continue; } final var matches = declarations.functionsBySignature.getOrDefault(functionKey, List.of()); if (matches.isEmpty()) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name(), "Barrel function entry '%s' in module %s does not resolve to a declaration".formatted( functionItem.name(), @@ -107,12 +109,13 @@ public final class PbsModuleVisibilityValidator { continue; } if (matches.size() > 1) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsLinkErrors.E_LINK_AMBIGUOUS_BARREL_ENTRY.name(), "Barrel function entry '%s' in module %s resolves ambiguously".formatted( functionItem.name(), displayModule(module.coordinates())), - functionItem.span()); + functionItem.span(), + List.of(new RelatedSpan("One matching function declaration is here", matches.getFirst()))); continue; } @@ -128,19 +131,21 @@ public final class PbsModuleVisibilityValidator { } final var symbolKey = new NonFunctionSymbolKey(symbolKind, nameTable.register(nonFunctionName(item))); - if (!seenNonFunctionEntries.add(symbolKey)) { - diagnostics.error( + final var firstNonFunctionEntry = seenNonFunctionEntries.putIfAbsent(symbolKey, item.span()); + if (firstNonFunctionEntry != null) { + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsLinkErrors.E_LINK_DUPLICATE_BARREL_ENTRY.name(), "Duplicate barrel entry '%s' in module %s".formatted( nonFunctionName(item), displayModule(module.coordinates())), - item.span()); + item.span(), + List.of(new RelatedSpan("First barrel entry is here", firstNonFunctionEntry))); continue; } final var matches = declarations.nonFunctionsByKindAndName.getOrDefault(symbolKey, List.of()); if (matches.isEmpty()) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name(), "Barrel entry '%s' in module %s does not resolve to a declaration".formatted( nonFunctionName(item), @@ -149,12 +154,13 @@ public final class PbsModuleVisibilityValidator { continue; } if (matches.size() > 1) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsLinkErrors.E_LINK_AMBIGUOUS_BARREL_ENTRY.name(), "Barrel entry '%s' in module %s resolves ambiguously".formatted( nonFunctionName(item), displayModule(module.coordinates())), - item.span()); + item.span(), + List.of(new RelatedSpan("One matching declaration is here", matches.getFirst()))); continue; } @@ -188,7 +194,7 @@ public final class PbsModuleVisibilityValidator { for (final var importItem : importDecl.items()) { final var importedNameId = nameTable.register(importItem.name()); if (!targetExports.publicNameIds.contains(importedNameId)) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name(), "Symbol '%s' is not public in module %s".formatted( importItem.name(), diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsBarrelParser.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsBarrelParser.java index 24559dd4..0b5167fd 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsBarrelParser.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsBarrelParser.java @@ -303,7 +303,7 @@ public final class PbsBarrelParser { } private void report(final PbsToken token, final ParseErrors parseErrors, final String message) { - diagnostics.error(parseErrors.name(), message, new Span(fileId, token.start(), token.end())); + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, parseErrors.name(), message, new Span(fileId, token.start(), token.end())); } private record ParsedReturnSpec( diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsExprParser.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsExprParser.java index 73ed5734..2a44a2e8 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsExprParser.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsExprParser.java @@ -630,7 +630,7 @@ final class PbsExprParser { } private void report(final PbsToken token, final ParseErrors parseErrors, final String message) { - diagnostics.error(parseErrors.name(), message, new Span(fileId, token.start(), token.end())); + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, parseErrors.name(), message, new Span(fileId, token.start(), token.end())); } private long parseLongOrDefault(final String text) { diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java index 528431d8..7372e9b6 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java @@ -5,11 +5,13 @@ import p.studio.compiler.pbs.lexer.PbsToken; import p.studio.compiler.pbs.lexer.PbsTokenKind; import p.studio.compiler.source.Span; import p.studio.compiler.source.diagnostics.DiagnosticSink; +import p.studio.compiler.source.diagnostics.RelatedSpan; import p.studio.compiler.source.identifiers.FileId; import p.studio.utilities.structures.ReadOnlyList; import java.util.ArrayList; -import java.util.HashSet; +import java.util.HashMap; +import java.util.List; /** * High-level manual parser for PBS source files. @@ -279,7 +281,7 @@ public final class PbsParser { private void parseRejectedAttributeList() { while (cursor.check(PbsTokenKind.LEFT_BRACKET)) { final var attributeSpan = parseAttribute(); - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, ParseErrors.E_PARSE_ATTRIBUTES_NOT_ALLOWED.name(), "Attributes are not allowed in ordinary .pbs source modules", attributeSpan); @@ -448,32 +450,44 @@ public final class PbsParser { consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after enum name"); final var cases = new ArrayList(); - final var caseLabels = new HashSet(); - final var explicitCaseIds = new HashSet(); + final var caseLabels = new HashMap(); + final var explicitCaseIds = new HashMap(); var hasExplicitCases = false; var hasImplicitCases = false; if (!cursor.check(PbsTokenKind.RIGHT_PAREN)) { do { final var caseName = consume(PbsTokenKind.IDENTIFIER, "Expected enum case label"); + final var caseNameSpan = span(caseName.start(), caseName.end()); Long explicitValue = null; if (cursor.match(PbsTokenKind.EQUAL)) { final var intToken = consume(PbsTokenKind.INT_LITERAL, "Expected integer literal after '=' in enum case"); + final var intTokenSpan = span(intToken.start(), intToken.end()); explicitValue = parseLongOrNull(intToken.lexeme()); hasExplicitCases = true; if (explicitValue == null) { report(intToken, ParseErrors.E_PARSE_INVALID_ENUM_FORM, "Invalid explicit enum identifier"); - } else if (!explicitCaseIds.add(explicitValue)) { - report(intToken, ParseErrors.E_PARSE_DUPLICATE_ENUM_CASE_ID, - "Duplicate explicit enum identifier '%s'".formatted(explicitValue)); + } else { + final var firstIdSpan = explicitCaseIds.putIfAbsent(explicitValue, intTokenSpan); + if (firstIdSpan != null) { + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, + ParseErrors.E_PARSE_DUPLICATE_ENUM_CASE_ID.name(), + "Duplicate explicit enum identifier '%s'".formatted(explicitValue), + intTokenSpan, + List.of(new RelatedSpan("First explicit enum identifier is here", firstIdSpan))); + } } } else { hasImplicitCases = true; } - if (!caseLabels.add(caseName.lexeme())) { - report(caseName, ParseErrors.E_PARSE_DUPLICATE_ENUM_CASE_LABEL, - "Duplicate enum case label '%s'".formatted(caseName.lexeme())); + final var firstLabelSpan = caseLabels.putIfAbsent(caseName.lexeme(), caseNameSpan); + if (firstLabelSpan != null) { + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, + ParseErrors.E_PARSE_DUPLICATE_ENUM_CASE_LABEL.name(), + "Duplicate enum case label '%s'".formatted(caseName.lexeme()), + caseNameSpan, + List.of(new RelatedSpan("First enum case label is here", firstLabelSpan))); } cases.add(new PbsAst.EnumCase(caseName.lexeme(), explicitValue, span(caseName.start(), cursor.previous().end()))); @@ -1162,7 +1176,7 @@ public final class PbsParser { * Reports a parser diagnostic at the given token span. */ private void report(final PbsToken token, final ParseErrors parseErrors, final String message) { - diagnostics.error(parseErrors.name(), message, new Span(fileId, token.start(), token.end())); + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, parseErrors.name(), message, new Span(fileId, token.start(), token.end())); } private record ParsedReturnSpec( diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationRuleValidator.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationRuleValidator.java index c0ff92a3..4215aa77 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationRuleValidator.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationRuleValidator.java @@ -46,7 +46,7 @@ final class PbsDeclarationRuleValidator { final var parameterNameId = nameTable.register(parameter.name()); final var first = seen.putIfAbsent(parameterNameId, parameter.span()); if (first != null) { - diagnostics.report( + p.studio.compiler.source.diagnostics.Diagnostics.report(diagnostics, Severity.Error, PbsSemanticsErrors.E_SEM_DUPLICATE_PARAMETER_NAME.name(), "Duplicate parameter name '%s' in %s".formatted(parameter.name(), ownerDescription), @@ -68,7 +68,7 @@ final class PbsDeclarationRuleValidator { final var caseNameId = nameTable.register(caseName); final var first = seenCases.putIfAbsent(caseNameId, errorDecl.span()); if (first != null) { - diagnostics.report( + p.studio.compiler.source.diagnostics.Diagnostics.report(diagnostics, Severity.Error, PbsSemanticsErrors.E_SEM_DUPLICATE_ERROR_CASE_LABEL.name(), "Duplicate error case label '%s' in '%s'".formatted(caseName, errorDecl.name()), @@ -85,7 +85,7 @@ final class PbsDeclarationRuleValidator { final var labelId = nameTable.register(enumCase.name()); final var firstLabel = seenLabels.putIfAbsent(labelId, enumCase.span()); if (firstLabel != null) { - diagnostics.report( + p.studio.compiler.source.diagnostics.Diagnostics.report(diagnostics, Severity.Error, PbsSemanticsErrors.E_SEM_DUPLICATE_ENUM_CASE_LABEL.name(), "Duplicate enum case label '%s' in '%s'".formatted(enumCase.name(), enumDecl.name()), @@ -96,7 +96,7 @@ final class PbsDeclarationRuleValidator { if (enumCase.explicitValue() != null) { final var firstId = seenIds.putIfAbsent(enumCase.explicitValue(), enumCase.span()); if (firstId != null) { - diagnostics.report( + p.studio.compiler.source.diagnostics.Diagnostics.report(diagnostics, Severity.Error, PbsSemanticsErrors.E_SEM_DUPLICATE_ENUM_CASE_ID.name(), "Duplicate enum case id '%s' in '%s'".formatted(enumCase.explicitValue(), enumDecl.name()), @@ -123,7 +123,7 @@ final class PbsDeclarationRuleValidator { void validateConstDeclaration(final PbsAst.ConstDecl constDecl) { if (constDecl.explicitType() == null) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_MISSING_CONST_TYPE_ANNOTATION.name(), "Const declaration '%s' must include an explicit type annotation".formatted(constDecl.name()), constDecl.span()); @@ -135,7 +135,7 @@ final class PbsDeclarationRuleValidator { } if (constDecl.initializer() == null) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_MISSING_CONST_INITIALIZER.name(), "Non-builtin const declaration '%s' must include an initializer".formatted(constDecl.name()), constDecl.span()); @@ -149,7 +149,7 @@ final class PbsDeclarationRuleValidator { final String ownerDescription, final Span span) { if (returnKind == PbsAst.ReturnKind.RESULT && PbsTypeSurfaceSemanticsValidator.isOptionalType(returnType)) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_INVALID_MIXED_OPTIONAL_RESULT_RETURN.name(), "Return surface for %s cannot combine 'result' with top-level 'optional' payload".formatted(ownerDescription), span); @@ -175,7 +175,7 @@ final class PbsDeclarationRuleValidator { final var labelId = nameTable.register(field.label()); final var first = seen.putIfAbsent(labelId, field.span()); if (first != null) { - diagnostics.report( + p.studio.compiler.source.diagnostics.Diagnostics.report(diagnostics, Severity.Error, PbsSemanticsErrors.E_SEM_DUPLICATE_RETURN_LABEL.name(), "Duplicate return label '%s' in %s".formatted(field.label(), ownerDescription), diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java index 9f4afdb3..611381cb 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsDeclarationSemanticsValidator.java @@ -106,7 +106,7 @@ public final class PbsDeclarationSemanticsValidator { binder.registerCtor(ctorScope, ctor.name(), PbsCallableShape.ctorShapeKey(ctor.parameters()), ctor.span()); if (PbsCtorReturnScanner.containsReturnStatement(ctor.body())) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_INVALID_RETURN_INSIDE_CTOR.name(), "Constructors cannot contain 'return' statements", ctor.span()); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowBodyAnalyzer.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowBodyAnalyzer.java index 55f67d1b..01922ec8 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowBodyAnalyzer.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowBodyAnalyzer.java @@ -170,7 +170,7 @@ final class PbsFlowBodyAnalyzer { true, this::analyzeBlock).type(); if (!typeOps.isBool(condition)) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_IF_NON_BOOL_CONDITION.name(), "If statement condition must have bool type", ifStatement.condition().span()); @@ -215,7 +215,7 @@ final class PbsFlowBodyAnalyzer { true, this::analyzeBlock).type(); if (!typeOps.compatible(fromType, iteratorType) || !typeOps.compatible(untilType, iteratorType)) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_FOR_TYPE_MISMATCH.name(), "For-loop bounds must match declared iterator type", span); @@ -234,7 +234,7 @@ final class PbsFlowBodyAnalyzer { true, this::analyzeBlock).type(); if (!typeOps.compatible(stepType, iteratorType)) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_FOR_TYPE_MISMATCH.name(), "For-loop step must match declared iterator type", stepExpression.span()); @@ -259,7 +259,7 @@ final class PbsFlowBodyAnalyzer { true, this::analyzeBlock).type(); if (!typeOps.isBool(condition)) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_WHILE_NON_BOOL_CONDITION.name(), "While condition must have bool type", whileStatement.condition().span()); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowExpressionAnalyzer.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowExpressionAnalyzer.java index 96849190..6f1f7aee 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowExpressionAnalyzer.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowExpressionAnalyzer.java @@ -265,7 +265,7 @@ final class PbsFlowExpressionAnalyzer { ExprUse.VALUE, true).type(); if (!typeOps.isBool(condition)) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_IF_NON_BOOL_CONDITION.name(), "If expression condition must have bool type", ifExpr.condition().span()); @@ -285,7 +285,7 @@ final class PbsFlowExpressionAnalyzer { true).type(); if (!typeOps.compatible(thenType, elseType)) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_IF_BRANCH_TYPE_MISMATCH.name(), "If expression branches must have compatible types", ifExpr.span()); @@ -318,7 +318,7 @@ final class PbsFlowExpressionAnalyzer { ExprUse.VALUE, true).type(); if (optional.kind() != Kind.OPTIONAL) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_ELSE_NON_OPTIONAL_LEFT.name(), "Left operand of 'else' must have optional type", elseExpr.optionalExpression().span()); @@ -349,7 +349,7 @@ final class PbsFlowExpressionAnalyzer { ExprUse.VALUE, true).type(); if (!typeOps.compatible(fallback, payload)) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_ELSE_FALLBACK_TYPE_MISMATCH.name(), "Fallback expression in 'else' must match optional payload type", elseExpr.fallbackExpression().span()); @@ -369,7 +369,7 @@ final class PbsFlowExpressionAnalyzer { ExprUse.VALUE, true).type(); if (source.kind() != Kind.RESULT) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_RESULT_PROPAGATE_NON_RESULT.name(), "Propagation operator '!' requires result type", propagateExpr.expression().span()); @@ -377,7 +377,7 @@ final class PbsFlowExpressionAnalyzer { } final var sourceError = source.errorType(); if (resultErrorName == null || sourceError == null || !resultErrorName.equals(sourceError.name())) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_RESULT_PROPAGATE_ERROR_MISMATCH.name(), "Propagation operator '!' requires matching result error type in enclosing callable", propagateExpr.span()); @@ -396,7 +396,7 @@ final class PbsFlowExpressionAnalyzer { } if (expression instanceof PbsAst.NoneExpr noneExpr) { if (expectedType == null || expectedType.kind() != Kind.OPTIONAL) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_NONE_WITHOUT_EXPECTED_OPTIONAL.name(), "'none' requires an expected optional type context", noneExpr.span()); @@ -465,12 +465,12 @@ final class PbsFlowExpressionAnalyzer { compatible.add(candidate); } if (compatible.size() > 1) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_APPLY_AMBIGUOUS_OVERLOAD.name(), "Bind target resolves ambiguously for callback type", bindExpr.span()); } else if (compatible.isEmpty()) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_APPLY_UNRESOLVED_OVERLOAD.name(), "Bind target does not resolve for callback type", bindExpr.span()); @@ -636,7 +636,7 @@ final class PbsFlowExpressionAnalyzer { final DiagnosticSink diagnostics) { final var candidates = callee.callables(); if (candidates.isEmpty()) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_APPLY_NON_CALLABLE_TARGET.name(), "Apply/call target is not callable", calleeSpan); @@ -651,7 +651,7 @@ final class PbsFlowExpressionAnalyzer { } if (compatible.isEmpty()) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_APPLY_UNRESOLVED_OVERLOAD.name(), "No callable overload matches the provided argument", wholeSpan); @@ -666,7 +666,7 @@ final class PbsFlowExpressionAnalyzer { return ExprResult.type(narrowed.getFirst().outputType()); } if (!narrowed.isEmpty()) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_APPLY_AMBIGUOUS_OVERLOAD.name(), "Callable overload resolution remains ambiguous", wholeSpan); @@ -675,7 +675,7 @@ final class PbsFlowExpressionAnalyzer { } if (compatible.size() > 1) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_APPLY_AMBIGUOUS_OVERLOAD.name(), "Callable overload resolution is ambiguous", wholeSpan); @@ -711,7 +711,7 @@ final class PbsFlowExpressionAnalyzer { if (enumCases != null && enumCases.contains(memberExpr.memberName())) { return ExprResult.type(TypeView.enumType(receiver.name())); } - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), "Invalid type member access", memberExpr.span()); @@ -720,7 +720,7 @@ final class PbsFlowExpressionAnalyzer { if (receiver.kind() == Kind.TUPLE) { if (receiver.tupleFields().size() <= 1) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), "Member projection is not defined for single-slot carrier values", memberExpr.span()); @@ -731,7 +731,7 @@ final class PbsFlowExpressionAnalyzer { return ExprResult.type(field.type()); } } - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), "Tuple label '%s' does not exist".formatted(memberExpr.memberName()), memberExpr.span()); @@ -750,7 +750,7 @@ final class PbsFlowExpressionAnalyzer { final var methods = struct.methods().get(memberExpr.memberName()); if (methods != null && !methods.isEmpty()) { if (use == ExprUse.VALUE) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_BARE_METHOD_EXTRACTION.name(), "Bare method extraction is not allowed in PBS core", memberExpr.span()); @@ -758,7 +758,7 @@ final class PbsFlowExpressionAnalyzer { } return ExprResult.callables(methods, true); } - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), "Struct member '%s' does not exist".formatted(memberExpr.memberName()), memberExpr.span()); @@ -773,7 +773,7 @@ final class PbsFlowExpressionAnalyzer { final var methods = service.methods().get(memberExpr.memberName()); if (methods != null && !methods.isEmpty()) { if (use == ExprUse.VALUE) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_BARE_METHOD_EXTRACTION.name(), "Bare method extraction is not allowed in PBS core", memberExpr.span()); @@ -781,7 +781,7 @@ final class PbsFlowExpressionAnalyzer { } return ExprResult.callables(methods, true); } - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), "Service member '%s' does not exist".formatted(memberExpr.memberName()), memberExpr.span()); @@ -796,7 +796,7 @@ final class PbsFlowExpressionAnalyzer { final var methods = contract.methods().get(memberExpr.memberName()); if (methods != null && !methods.isEmpty()) { if (use == ExprUse.VALUE) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_BARE_METHOD_EXTRACTION.name(), "Bare method extraction is not allowed in PBS core", memberExpr.span()); @@ -804,14 +804,14 @@ final class PbsFlowExpressionAnalyzer { } return ExprResult.callables(methods, true); } - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), "Contract member '%s' does not exist".formatted(memberExpr.memberName()), memberExpr.span()); return ExprResult.type(TypeView.unknown()); } - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), "Invalid member access target", memberExpr.span()); @@ -842,7 +842,7 @@ final class PbsFlowExpressionAnalyzer { final var selectorComparable = typeOps.isScalarComparable(selector) || selector.kind() == Kind.ENUM; if (!selectorComparable) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_SWITCH_SELECTOR_INVALID.name(), "Switch selector type is not supported", switchExpr.selector().span()); @@ -857,7 +857,7 @@ final class PbsFlowExpressionAnalyzer { if (arm.pattern() instanceof PbsAst.WildcardSwitchPattern) { hasWildcard = true; } else if (!seenPatterns.add(patternKey)) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_SWITCH_DUPLICATE_PATTERN.name(), "Duplicate switch pattern", arm.pattern().span()); @@ -865,7 +865,7 @@ final class PbsFlowExpressionAnalyzer { final var patternType = switchPatternType(arm.pattern(), model, diagnostics); if (patternType != null && !typeOps.compatible(patternType, selector)) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_SWITCH_PATTERN_TYPE_MISMATCH.name(), "Switch pattern is not compatible with selector type", arm.pattern().span()); @@ -883,7 +883,7 @@ final class PbsFlowExpressionAnalyzer { if (armType == null) { armType = currentArmType; } else if (!typeOps.compatible(currentArmType, armType)) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_SWITCH_ARM_TYPE_MISMATCH.name(), "Switch arm block types must be compatible", arm.span()); @@ -907,7 +907,7 @@ final class PbsFlowExpressionAnalyzer { } } if (!exhaustive) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_SWITCH_NON_EXHAUSTIVE.name(), "Switch expression in value position must be exhaustive", switchExpr.span()); @@ -952,7 +952,7 @@ final class PbsFlowExpressionAnalyzer { final var caseName = segments.get(1); final var enumCases = model.enums.get(enumName); if (enumCases == null || !enumCases.contains(caseName)) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_SWITCH_PATTERN_TYPE_MISMATCH.name(), "Enum case pattern '%s.%s' does not resolve".formatted(enumName, caseName), enumCaseSwitchPattern.span()); @@ -998,14 +998,14 @@ final class PbsFlowExpressionAnalyzer { ExprUse.VALUE, true).type(); if (sourceType.kind() != Kind.RESULT) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_HANDLE_NON_RESULT.name(), "Handle requires result expression", handleExpr.value().span()); return TypeView.unknown(); } if (resultErrorName == null) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_HANDLE_ERROR_MISMATCH.name(), "Handle requires enclosing callable with result return", handleExpr.span()); @@ -1025,12 +1025,12 @@ final class PbsFlowExpressionAnalyzer { final var errorName = segments.getFirst(); final var caseName = segments.get(1); if (!errorName.equals(sourceErrorName) || !sourceCases.contains(caseName)) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_HANDLE_ERROR_MISMATCH.name(), "Handle arm pattern does not match source result error type", arm.pattern().span()); } else if (!matchedCases.add(caseName)) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_HANDLE_ERROR_MISMATCH.name(), "Handle arm duplicates same error case pattern", arm.pattern().span()); @@ -1040,7 +1040,7 @@ final class PbsFlowExpressionAnalyzer { if (arm.remapTarget() != null) { if (!matchesTargetError(arm.remapTarget(), resultErrorName, model)) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_HANDLE_ERROR_MISMATCH.name(), "Handle remap target must match enclosing callable result error type", arm.remapTarget().span()); @@ -1051,7 +1051,7 @@ final class PbsFlowExpressionAnalyzer { } if (!hasWildcard && !sourceCases.isEmpty() && !matchedCases.containsAll(sourceCases)) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_HANDLE_ERROR_MISMATCH.name(), "Handle mapping is not exhaustive for source result error cases", handleExpr.span()); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsNamespaceBinder.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsNamespaceBinder.java index 90c13388..8ee52873 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsNamespaceBinder.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsNamespaceBinder.java @@ -50,7 +50,7 @@ final class PbsNamespaceBinder { return; } - diagnostics.report( + p.studio.compiler.source.diagnostics.Diagnostics.report(diagnostics, Severity.Error, PbsSemanticsErrors.E_SEM_DUPLICATE_CALLABLE_SHAPE.name(), "Duplicate callable declaration '%s' with shape %s in %s".formatted(callableName, shape, scope.label()), @@ -70,7 +70,7 @@ final class PbsNamespaceBinder { return; } - diagnostics.report( + p.studio.compiler.source.diagnostics.Diagnostics.report(diagnostics, Severity.Error, PbsSemanticsErrors.E_SEM_DUPLICATE_CALLABLE_SHAPE.name(), "Duplicate constructor declaration '%s' with shape %s in %s".formatted(ctorName, shape, scope.label()), @@ -90,7 +90,7 @@ final class PbsNamespaceBinder { return; } - diagnostics.report( + p.studio.compiler.source.diagnostics.Diagnostics.report(diagnostics, Severity.Error, PbsSemanticsErrors.E_SEM_DUPLICATE_DECLARATION.name(), "Duplicate %s declaration '%s' in %s".formatted(declarationKind, declarationName, namespaceName), diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsTypeSurfaceSemanticsValidator.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsTypeSurfaceSemanticsValidator.java index 78c6cc66..c12781f6 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsTypeSurfaceSemanticsValidator.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsTypeSurfaceSemanticsValidator.java @@ -24,7 +24,7 @@ final class PbsTypeSurfaceSemanticsValidator { switch (typeRef.kind()) { case OPTIONAL -> { if (typeRef.inner() == null || typeRef.inner().kind() == PbsAst.TypeRefKind.UNIT) { - diagnostics.error( + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_INVALID_OPTIONAL_VOID_TYPE_SURFACE.name(), "Invalid optional type surface in %s: 'optional void' is not allowed".formatted(ownerDescription), typeRef.span()); 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 861f7268..dd04c491 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 @@ -63,7 +63,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { final var barrelAst = parseBarrelFile(fId, utf8Content, diagnostics); moduleUnit.barrels.add(new PbsModuleVisibilityValidator.BarrelFile(fId, barrelAst)); } else { - diagnostics.error( + 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)); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsDiagnosticsContractTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsDiagnosticsContractTest.java new file mode 100644 index 00000000..0f76bf22 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsDiagnosticsContractTest.java @@ -0,0 +1,83 @@ +package p.studio.compiler.pbs; + +import org.junit.jupiter.api.Test; +import p.studio.compiler.pbs.lexer.LexErrors; +import p.studio.compiler.pbs.lexer.PbsLexer; +import p.studio.compiler.pbs.linking.PbsLinkErrors; +import p.studio.compiler.pbs.linking.PbsModuleVisibilityValidator; +import p.studio.compiler.pbs.parser.PbsBarrelParser; +import p.studio.compiler.pbs.parser.PbsParser; +import p.studio.compiler.pbs.semantics.PbsSemanticsErrors; +import p.studio.compiler.source.diagnostics.DiagnosticPhase; +import p.studio.compiler.source.diagnostics.DiagnosticSink; +import p.studio.compiler.source.identifiers.FileId; +import p.studio.utilities.structures.ReadOnlyList; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PbsDiagnosticsContractTest { + + @Test + void shouldTagLexerDiagnosticsWithSyntaxPhase() { + final var diagnostics = DiagnosticSink.empty(); + + PbsLexer.lex("$", new FileId(0), diagnostics); + + final var diagnostic = diagnostics.stream() + .filter(d -> d.getCode().equals(LexErrors.E_LEX_INVALID_CHAR.name())) + .findFirst() + .orElseThrow(); + assertEquals(DiagnosticPhase.SYNTAX, diagnostic.getPhase()); + assertEquals(diagnostic.getCode(), diagnostic.getTemplateId()); + assertTrue(diagnostic.getPlaceholders().isEmpty()); + } + + @Test + void shouldTagSemanticsDiagnosticsWithStaticSemanticsPhase() { + final var source = """ + fn sum(a: int) {} + fn sum(a: int) {} + """; + final var diagnostics = DiagnosticSink.empty(); + + new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics); + + final var diagnostic = diagnostics.stream() + .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_CALLABLE_SHAPE.name())) + .findFirst() + .orElseThrow(); + assertEquals(DiagnosticPhase.STATIC_SEMANTICS, diagnostic.getPhase()); + } + + @Test + void shouldTagLinkingDiagnosticsAndExposeRelatedSpanForDuplicateBarrelEntry() { + final var diagnostics = DiagnosticSink.empty(); + final var sourceFileId = new FileId(1); + final var barrelFileId = new FileId(2); + final var source = """ + fn run() {} + """; + final var barrel = """ + pub fn run(); + pub fn run(); + """; + final var sourceAst = PbsParser.parse(PbsLexer.lex(source, sourceFileId, diagnostics), sourceFileId, diagnostics); + final var barrelAst = PbsBarrelParser.parse(PbsLexer.lex(barrel, barrelFileId, diagnostics), barrelFileId, diagnostics); + final var module = new PbsModuleVisibilityValidator.ModuleUnit( + new PbsModuleVisibilityValidator.ModuleCoordinates("app", ReadOnlyList.wrap(List.of("core"))), + ReadOnlyList.wrap(List.of(new PbsModuleVisibilityValidator.SourceFile(sourceFileId, sourceAst))), + ReadOnlyList.wrap(List.of(new PbsModuleVisibilityValidator.BarrelFile(barrelFileId, barrelAst)))); + + new PbsModuleVisibilityValidator().validate(ReadOnlyList.wrap(List.of(module)), diagnostics); + + final var diagnostic = diagnostics.stream() + .filter(d -> d.getCode().equals(PbsLinkErrors.E_LINK_DUPLICATE_BARREL_ENTRY.name())) + .findFirst() + .orElseThrow(); + assertEquals(DiagnosticPhase.LINKING, diagnostic.getPhase()); + assertEquals(1, diagnostic.getRelated().size()); + } +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/Diagnostic.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/Diagnostic.java index ed05faba..60f71e18 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/Diagnostic.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/Diagnostic.java @@ -5,11 +5,16 @@ import p.studio.compiler.source.Span; import p.studio.utilities.structures.ReadOnlyList; import java.util.List; +import java.util.Map; +import java.util.Objects; @Getter public class Diagnostic { private final Severity severity; private final String code; + private final DiagnosticPhase phase; + private final String templateId; + private final Map placeholders; private final String message; private final Span span; private final ReadOnlyList related; @@ -20,10 +25,43 @@ public class Diagnostic { final String message, final Span span, final List related) { - this.severity = severity; - this.code = code; - this.message = message; - this.span = span; - this.related = ReadOnlyList.wrap(related); + this( + severity, + code, + DiagnosticPhase.fromCode(code), + code, + Map.of(), + message, + span, + related); + } + + public Diagnostic( + final Severity severity, + final String code, + final DiagnosticPhase phase, + final String templateId, + final Map placeholders, + final String message, + final Span span, + final List related) { + this.severity = Objects.requireNonNull(severity); + this.code = Objects.requireNonNull(code); + this.phase = Objects.requireNonNull(phase); + this.templateId = Objects.requireNonNull(templateId); + this.placeholders = Map.copyOf(Objects.requireNonNull(placeholders)); + this.message = Objects.requireNonNull(message); + this.span = Objects.requireNonNull(span); + this.related = ReadOnlyList.wrap(List.copyOf(Objects.requireNonNull(related))); + } + + @Override + public String toString() { + return "%s[%s:%s] %s @ %s".formatted( + severity, + phase.id(), + code, + message, + span); } } diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/DiagnosticPhase.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/DiagnosticPhase.java new file mode 100644 index 00000000..54ae1b2d --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/DiagnosticPhase.java @@ -0,0 +1,53 @@ +package p.studio.compiler.source.diagnostics; + +import java.util.Locale; + +public enum DiagnosticPhase { + SYNTAX("syntax"), + STATIC_SEMANTICS("static-semantics"), + MANIFEST_IMPORT_RESOLUTION("manifest-import-resolution"), + LINKING("linking"), + HOST_ADMISSION("host-admission"), + LOAD_FACING_REJECTION("load-facing-rejection"), + UNKNOWN("unknown"), + ; + + private final String id; + + DiagnosticPhase(final String id) { + this.id = id; + } + + public String id() { + return id; + } + + public static DiagnosticPhase fromCode(final String code) { + if (code == null || code.isBlank()) { + return UNKNOWN; + } + + final var normalized = code.toUpperCase(Locale.ROOT); + if (normalized.startsWith("E_PARSE_") || normalized.startsWith("E_LEX_") + || normalized.startsWith("W_PARSE_") || normalized.startsWith("W_LEX_")) { + return SYNTAX; + } + if (normalized.startsWith("E_SEM_") || normalized.startsWith("W_SEM_")) { + return STATIC_SEMANTICS; + } + if (normalized.startsWith("E_LINK_") || normalized.startsWith("W_LINK_")) { + return LINKING; + } + if (normalized.startsWith("E_MANIFEST_") || normalized.startsWith("W_MANIFEST_") + || normalized.startsWith("E_IMPORT_") || normalized.startsWith("W_IMPORT_")) { + return MANIFEST_IMPORT_RESOLUTION; + } + if (normalized.startsWith("E_HOST_") || normalized.startsWith("W_HOST_")) { + return HOST_ADMISSION; + } + if (normalized.startsWith("E_LOAD_") || normalized.startsWith("W_LOAD_")) { + return LOAD_FACING_REJECTION; + } + return UNKNOWN; + } +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/DiagnosticSink.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/DiagnosticSink.java index f51929e2..3246b188 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/DiagnosticSink.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/DiagnosticSink.java @@ -6,6 +6,7 @@ import p.studio.utilities.structures.ReadOnlyCollection; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Objects; public class DiagnosticSink implements ReadOnlyCollection { @@ -39,20 +40,29 @@ public class DiagnosticSink implements ReadOnlyCollection { public DiagnosticSink report( final Severity severity, final String code, + final DiagnosticPhase phase, + final String templateId, + final Map placeholders, final String message, final Span span) { - return report(severity, code, message, span, List.of()); + return report(severity, code, phase, templateId, placeholders, message, span, List.of()); } public DiagnosticSink report( final Severity severity, final String code, + final DiagnosticPhase phase, + final String templateId, + final Map placeholders, final String message, final Span span, final List related) { return report(new Diagnostic( Objects.requireNonNull(severity), Objects.requireNonNull(code), + Objects.requireNonNull(phase), + Objects.requireNonNull(templateId), + Map.copyOf(Objects.requireNonNull(placeholders)), Objects.requireNonNull(message), Objects.requireNonNull(span), List.copyOf(Objects.requireNonNull(related)))); @@ -60,16 +70,61 @@ public class DiagnosticSink implements ReadOnlyCollection { public DiagnosticSink error( final String code, + final String templateId, + final Map placeholders, final String message, final Span span) { - return report(Severity.Error, code, message, span); + return report(Severity.Error, code, DiagnosticPhase.fromCode(code), templateId, placeholders, message, span); + } + + public DiagnosticSink error( + final String code, + final String templateId, + final Map placeholders, + final String message, + final Span span, + final List related) { + return report(Severity.Error, code, DiagnosticPhase.fromCode(code), templateId, placeholders, message, span, related); + } + + public DiagnosticSink error( + final DiagnosticPhase phase, + final String code, + final String templateId, + final Map placeholders, + final String message, + final Span span) { + return report(Severity.Error, code, phase, templateId, placeholders, message, span); + } + + public DiagnosticSink error( + final DiagnosticPhase phase, + final String code, + final String templateId, + final Map placeholders, + final String message, + final Span span, + final List related) { + return report(Severity.Error, code, phase, templateId, placeholders, message, span, related); } public DiagnosticSink warning( final String code, + final String templateId, + final Map placeholders, final String message, final Span span) { - return report(Severity.Warning, code, message, span); + return report(Severity.Warning, code, DiagnosticPhase.fromCode(code), templateId, placeholders, message, span); + } + + public DiagnosticSink warning( + final DiagnosticPhase phase, + final String code, + final String templateId, + final Map placeholders, + final String message, + final Span span) { + return report(Severity.Warning, code, phase, templateId, placeholders, message, span); } public DiagnosticSink merge(final DiagnosticSink diagnostics) { diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/Diagnostics.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/Diagnostics.java new file mode 100644 index 00000000..cfc043f4 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/diagnostics/Diagnostics.java @@ -0,0 +1,54 @@ +package p.studio.compiler.source.diagnostics; + +import p.studio.compiler.source.Span; + +import java.util.List; +import java.util.Map; + +public final class Diagnostics { + private Diagnostics() { + } + + public static DiagnosticSink error( + final DiagnosticSink sink, + final String code, + final String message, + final Span span) { + return sink.error(code, code, Map.of(), message, span); + } + + public static DiagnosticSink error( + final DiagnosticSink sink, + final String code, + final String message, + final Span span, + final List related) { + return sink.error(code, code, Map.of(), message, span, related); + } + + public static DiagnosticSink warning( + final DiagnosticSink sink, + final String code, + final String message, + final Span span) { + return sink.warning(code, code, Map.of(), message, span); + } + + public static DiagnosticSink report( + final DiagnosticSink sink, + final Severity severity, + final String code, + final String message, + final Span span, + final List related) { + return sink.report( + severity, + code, + DiagnosticPhase.fromCode(code), + code, + Map.of(), + message, + span, + related); + } +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/diagnostics/DiagnosticSinkTest.java b/prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/diagnostics/DiagnosticSinkTest.java index 94f17fac..eba84ed6 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/diagnostics/DiagnosticSinkTest.java +++ b/prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/diagnostics/DiagnosticSinkTest.java @@ -3,6 +3,9 @@ package p.studio.compiler.source.diagnostics; import org.junit.jupiter.api.Test; import p.studio.compiler.source.Span; +import java.util.List; +import java.util.Map; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -12,8 +15,8 @@ class DiagnosticSinkTest { void shouldReportAndCountDiagnostics() { final var sink = DiagnosticSink.empty(); - sink.error("E001", "Unknown symbol", Span.none()); - sink.warning("W001", "Unused declaration", Span.none()); + sink.error("E001", "E001", Map.of(), "Unknown symbol", Span.none()); + sink.warning("W001", "W001", Map.of(), "Unused declaration", Span.none()); assertEquals(2, sink.size()); assertTrue(sink.hasErrors()); @@ -25,9 +28,9 @@ class DiagnosticSinkTest { @Test void shouldMergeDiagnostics() { final var a = DiagnosticSink.empty() - .warning("W010", "Unused import", Span.none()); + .warning("W010", "W010", Map.of(), "Unused import", Span.none()); final var b = DiagnosticSink.empty() - .error("E999", "Invalid type", Span.none()); + .error("E999", "E999", Map.of(), "Invalid type", Span.none()); a.merge(b); @@ -39,4 +42,57 @@ class DiagnosticSinkTest { assertEquals(1, b.errorCount()); assertEquals(0, b.warningCount()); } + + @Test + void shouldPopulateContractFieldsForLegacyEmission() { + final var sink = DiagnosticSink.empty(); + + sink.error("E_PARSE_EXPECTED_TOKEN", "E_PARSE_EXPECTED_TOKEN", Map.of(), "Expected token", Span.none()); + + final var diagnostic = sink.stream().findFirst().orElseThrow(); + assertEquals("E_PARSE_EXPECTED_TOKEN", diagnostic.getCode()); + assertEquals(DiagnosticPhase.SYNTAX, diagnostic.getPhase()); + assertEquals("E_PARSE_EXPECTED_TOKEN", diagnostic.getTemplateId()); + assertTrue(diagnostic.getPlaceholders().isEmpty()); + assertEquals("Expected token", diagnostic.getMessage()); + assertTrue(diagnostic.getSpan().isNone()); + } + + @Test + void shouldKeepExplicitPhaseTemplateAndPlaceholdersStable() { + final var sink = DiagnosticSink.empty(); + + sink.error( + DiagnosticPhase.LINKING, + "E_LINK_UNRESOLVED_BARREL_ENTRY", + "pbs.link.unresolved-barrel-entry", + Map.of("module", "@app:core", "symbol", "foo"), + "Barrel entry 'foo' does not resolve", + Span.none()); + + final var diagnostic = sink.stream().findFirst().orElseThrow(); + assertEquals(DiagnosticPhase.LINKING, diagnostic.getPhase()); + assertEquals("pbs.link.unresolved-barrel-entry", diagnostic.getTemplateId()); + assertEquals(Map.of("module", "@app:core", "symbol", "foo"), diagnostic.getPlaceholders()); + } + + @Test + void shouldReportRelatedSpansAndRenderPhaseInToString() { + final var sink = DiagnosticSink.empty(); + final var related = new RelatedSpan("First declaration is here", Span.none()); + + sink.error( + "E_SEM_DUPLICATE_DECLARATION", + "E_SEM_DUPLICATE_DECLARATION", + Map.of(), + "Duplicate declaration", + Span.none(), + List.of(related)); + + final var diagnostic = sink.stream().findFirst().orElseThrow(); + assertEquals(1, diagnostic.getRelated().size()); + assertEquals("First declaration is here", diagnostic.getRelated().getFirst().getMessage()); + assertTrue(diagnostic.toString().contains("static-semantics")); + assertTrue(diagnostic.toString().contains("E_SEM_DUPLICATE_DECLARATION")); + } }