implements PR009
This commit is contained in:
parent
20c5cd7841
commit
63b6ad68c4
@ -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.
|
||||
@ -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`.
|
||||
@ -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`.
|
||||
@ -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<String, PbsTokenKind> buildKeywords() {
|
||||
|
||||
@ -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<NonFunctionSymbolKey> seenNonFunctionEntries = new HashSet<>();
|
||||
final Set<FunctionSymbolKey> seenFunctionEntries = new HashSet<>();
|
||||
final Map<NonFunctionSymbolKey, Span> seenNonFunctionEntries = new HashMap<>();
|
||||
final Map<FunctionSymbolKey, Span> 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(),
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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<PbsAst.EnumCase>();
|
||||
final var caseLabels = new HashSet<String>();
|
||||
final var explicitCaseIds = new HashSet<Long>();
|
||||
final var caseLabels = new HashMap<String, Span>();
|
||||
final var explicitCaseIds = new HashMap<Long, Span>();
|
||||
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(
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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<String, String> placeholders;
|
||||
private final String message;
|
||||
private final Span span;
|
||||
private final ReadOnlyList<RelatedSpan> related;
|
||||
@ -20,10 +25,43 @@ public class Diagnostic {
|
||||
final String message,
|
||||
final Span span,
|
||||
final List<RelatedSpan> 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<String, String> placeholders,
|
||||
final String message,
|
||||
final Span span,
|
||||
final List<RelatedSpan> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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<Diagnostic> {
|
||||
@ -39,20 +40,29 @@ public class DiagnosticSink implements ReadOnlyCollection<Diagnostic> {
|
||||
public DiagnosticSink report(
|
||||
final Severity severity,
|
||||
final String code,
|
||||
final DiagnosticPhase phase,
|
||||
final String templateId,
|
||||
final Map<String, String> 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<String, String> placeholders,
|
||||
final String message,
|
||||
final Span span,
|
||||
final List<RelatedSpan> 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<Diagnostic> {
|
||||
|
||||
public DiagnosticSink error(
|
||||
final String code,
|
||||
final String templateId,
|
||||
final Map<String, String> 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<String, String> placeholders,
|
||||
final String message,
|
||||
final Span span,
|
||||
final List<RelatedSpan> 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<String, String> 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<String, String> placeholders,
|
||||
final String message,
|
||||
final Span span,
|
||||
final List<RelatedSpan> related) {
|
||||
return report(Severity.Error, code, phase, templateId, placeholders, message, span, related);
|
||||
}
|
||||
|
||||
public DiagnosticSink warning(
|
||||
final String code,
|
||||
final String templateId,
|
||||
final Map<String, String> 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<String, String> placeholders,
|
||||
final String message,
|
||||
final Span span) {
|
||||
return report(Severity.Warning, code, phase, templateId, placeholders, message, span);
|
||||
}
|
||||
|
||||
public DiagnosticSink merge(final DiagnosticSink diagnostics) {
|
||||
|
||||
@ -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<RelatedSpan> 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<RelatedSpan> related) {
|
||||
return sink.report(
|
||||
severity,
|
||||
code,
|
||||
DiagnosticPhase.fromCode(code),
|
||||
code,
|
||||
Map.of(),
|
||||
message,
|
||||
span,
|
||||
related);
|
||||
}
|
||||
}
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user