diff --git a/docs/pbs/pull-requests/PR-013-pbs-import-unresolved-diagnostics.md b/docs/pbs/pull-requests/PR-013-pbs-import-unresolved-diagnostics.md deleted file mode 100644 index b865b986..00000000 --- a/docs/pbs/pull-requests/PR-013-pbs-import-unresolved-diagnostics.md +++ /dev/null @@ -1,44 +0,0 @@ -# PR-013 - PBS Unresolved Import Diagnostics - -## Briefing - -Import resolution currently ignores missing target modules in frontend linking validation. This PR enforces deterministic diagnostics for unresolved module imports and unresolved imported symbols. - -## Motivation - -Silent import misses break the diagnostics contract and defer user-actionable errors. - -## Target - -- Module import validation in `PbsModuleVisibilityValidator`. -- Linking diagnostic coverage for import failures. - -## Scope - -- Emit errors for missing target module references. -- Keep existing public-symbol checks. -- Preserve deterministic, source-first attribution. - -## Method - -- Extend import validation to detect absent target modules. -- Report diagnostics on the importing site. -- Keep related-span behavior where applicable. - -## Acceptance Criteria - -- Importing a non-existent module always emits deterministic linking/import diagnostic. -- Importing a non-public symbol still emits existing diagnostic. -- Diagnostic attribution points to import site. -- No silent pass for unresolved imports remains. - -## Tests - -- Add tests for missing module import. -- Add tests for missing symbol import in existing module. -- Verify phase and diagnostic code stability. - -## Non-Goals - -- Full manifest/dependency resolver redesign. -- Loader/runtime capability checks. diff --git a/docs/pbs/pull-requests/PR-014-pbs-switch-wildcard-rules.md b/docs/pbs/pull-requests/PR-014-pbs-switch-wildcard-rules.md deleted file mode 100644 index f98a716b..00000000 --- a/docs/pbs/pull-requests/PR-014-pbs-switch-wildcard-rules.md +++ /dev/null @@ -1,42 +0,0 @@ -# PR-014 - PBS Switch Wildcard Rule Enforcement - -## Briefing - -Switch semantics do not currently enforce all wildcard constraints. This PR enforces: at most one wildcard arm, and rejection of mixed `default` and `_` in the same switch. - -## Motivation - -Wildcard ambiguity weakens deterministic static semantics and conformance. - -## Target - -- Switch expression semantic validation in flow analyzer. -- Required static diagnostics for wildcard rule violations. - -## Scope - -- Enforce wildcard-count and wildcard-mixing rules. -- Preserve existing selector/type compatibility checks. - -## Method - -- Track wildcard token form used per switch arm. -- Emit diagnostics for duplicate wildcard and mixed wildcard spellings. -- Keep existing exhaustiveness behavior. - -## Acceptance Criteria - -- Two wildcard arms are rejected deterministically. -- `default` + `_` in one switch is rejected deterministically. -- Existing duplicate-pattern logic for non-wildcards remains unchanged. - -## Tests - -- Add tests for duplicate wildcard arms. -- Add tests for mixed `default`/`_` arms. -- Run semantic control-flow suite. - -## Non-Goals - -- New switch syntax. -- Runtime switch lowering changes. diff --git a/docs/pbs/pull-requests/PR-015-pbs-this-self-context-validation.md b/docs/pbs/pull-requests/PR-015-pbs-this-self-context-validation.md deleted file mode 100644 index ade295a0..00000000 --- a/docs/pbs/pull-requests/PR-015-pbs-this-self-context-validation.md +++ /dev/null @@ -1,43 +0,0 @@ -# PR-015 - PBS this/Self Context Validation - -## Briefing - -The frontend accepts `this` and `Self` outside allowed contexts without required diagnostics. This PR adds explicit static checks for invalid usage. - -## Motivation - -Context-free acceptance of `this`/`Self` is non-conformant and creates type ambiguity. - -## Target - -- `this` expression analysis. -- `Self` type-surface analysis in declarations. -- Error catalog and deterministic diagnostics. - -## Scope - -- Reject `this` outside struct method, service method, and ctor bodies. -- Reject `Self` outside struct/service method declarations and ctors. - -## Method - -- Add callable-context metadata into flow/type validation. -- Emit dedicated diagnostics for invalid `this` and invalid `Self` usage. -- Keep valid owner contexts unchanged. - -## Acceptance Criteria - -- Invalid `this` usage emits deterministic static diagnostic. -- Invalid `Self` usage emits deterministic static diagnostic. -- Valid struct/service/ctor contexts remain accepted. - -## Tests - -- Add positive tests for valid `this`/`Self` contexts. -- Add negative tests for top-level functions and unrelated declarations. -- Run declaration + flow semantic suites. - -## Non-Goals - -- Introducing new receiver features. -- Altering `Self` syntax. 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 4215aa77..bc55a568 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 @@ -29,12 +29,20 @@ final class PbsDeclarationRuleValidator { final PbsAst.ReturnKind returnKind, final PbsAst.TypeRef returnType, final PbsAst.TypeRef resultErrorType, - final Span span) { + final Span span, + final boolean allowSelf) { validateParameters(parameters, "callable '%s'".formatted(callableName)); - validateReturnSurface(returnKind, returnType, resultErrorType, "callable '%s'".formatted(callableName), span); + validateReturnSurface( + returnKind, + returnType, + resultErrorType, + "callable '%s'".formatted(callableName), + span, + allowSelf); PbsTypeSurfaceSemanticsValidator.validateTypeSurfaceRecursive( parameters.stream().map(PbsAst.Parameter::typeRef).toList(), "callable '%s'".formatted(callableName), + allowSelf, diagnostics); } @@ -58,8 +66,9 @@ final class PbsDeclarationRuleValidator { void validateTypeSurfaces( final List typeRoots, - final String ownerDescription) { - PbsTypeSurfaceSemanticsValidator.validateTypeSurfaceRecursive(typeRoots, ownerDescription, diagnostics); + final String ownerDescription, + final boolean allowSelf) { + PbsTypeSurfaceSemanticsValidator.validateTypeSurfaceRecursive(typeRoots, ownerDescription, allowSelf, diagnostics); } void validateErrorDeclaration(final PbsAst.ErrorDecl errorDecl) { @@ -114,10 +123,12 @@ final class PbsDeclarationRuleValidator { callbackDecl.returnType(), callbackDecl.resultErrorType(), "callback '%s'".formatted(callbackDecl.name()), - callbackDecl.span()); + callbackDecl.span(), + false); PbsTypeSurfaceSemanticsValidator.validateTypeSurfaceRecursive( callbackDecl.parameters().stream().map(PbsAst.Parameter::typeRef).toList(), "callback '%s'".formatted(callbackDecl.name()), + false, diagnostics); } @@ -131,6 +142,7 @@ final class PbsDeclarationRuleValidator { PbsTypeSurfaceSemanticsValidator.validateTypeSurfaceRecursive( List.of(constDecl.explicitType()), "const '%s'".formatted(constDecl.name()), + false, diagnostics); } @@ -147,7 +159,8 @@ final class PbsDeclarationRuleValidator { final PbsAst.TypeRef returnType, final PbsAst.TypeRef resultErrorType, final String ownerDescription, - final Span span) { + final Span span, + final boolean allowSelf) { if (returnKind == PbsAst.ReturnKind.RESULT && PbsTypeSurfaceSemanticsValidator.isOptionalType(returnType)) { p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, PbsSemanticsErrors.E_SEM_INVALID_MIXED_OPTIONAL_RESULT_RETURN.name(), @@ -160,10 +173,10 @@ final class PbsDeclarationRuleValidator { } if (returnType != null) { - PbsTypeSurfaceSemanticsValidator.validateTypeSurfaceRecursive(List.of(returnType), ownerDescription, diagnostics); + PbsTypeSurfaceSemanticsValidator.validateTypeSurfaceRecursive(List.of(returnType), ownerDescription, allowSelf, diagnostics); } if (resultErrorType != null) { - PbsTypeSurfaceSemanticsValidator.validateTypeSurfaceRecursive(List.of(resultErrorType), ownerDescription, diagnostics); + PbsTypeSurfaceSemanticsValidator.validateTypeSurfaceRecursive(List.of(resultErrorType), ownerDescription, allowSelf, diagnostics); } } 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 611381cb..dd51f246 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 @@ -23,6 +23,7 @@ public final class PbsDeclarationSemanticsValidator { functionDecl.returnType(), functionDecl.resultErrorType(), functionDecl.span(), + false, binder, rules); continue; @@ -83,6 +84,10 @@ public final class PbsDeclarationSemanticsValidator { final PbsDeclarationRuleValidator rules, final DiagnosticSink diagnostics) { final var ownerNameId = nameTable.register(structDecl.name()); + rules.validateTypeSurfaces( + structDecl.fields().stream().map(PbsAst.StructField::typeRef).toList(), + "struct '%s'".formatted(structDecl.name()), + false); final var methodScope = PbsCallableScope.structMethods(ownerNameId); for (final var method : structDecl.methods()) { validateCallable( @@ -93,6 +98,7 @@ public final class PbsDeclarationSemanticsValidator { method.returnType(), method.resultErrorType(), method.span(), + true, binder, rules); } @@ -102,7 +108,8 @@ public final class PbsDeclarationSemanticsValidator { rules.validateParameters(ctor.parameters(), "ctor '%s'".formatted(ctor.name())); rules.validateTypeSurfaces( ctor.parameters().stream().map(PbsAst.Parameter::typeRef).toList(), - "ctor '%s'".formatted(ctor.name())); + "ctor '%s'".formatted(ctor.name()), + true); binder.registerCtor(ctorScope, ctor.name(), PbsCallableShape.ctorShapeKey(ctor.parameters()), ctor.span()); if (PbsCtorReturnScanner.containsReturnStatement(ctor.body())) { @@ -128,6 +135,7 @@ public final class PbsDeclarationSemanticsValidator { method.returnType(), method.resultErrorType(), method.span(), + true, binder, rules); } @@ -147,6 +155,7 @@ public final class PbsDeclarationSemanticsValidator { signature.returnType(), signature.resultErrorType(), signature.span(), + false, binder, rules); } @@ -167,6 +176,7 @@ public final class PbsDeclarationSemanticsValidator { method.returnType(), method.resultErrorType(), method.span(), + false, binder, rules); } @@ -180,9 +190,10 @@ public final class PbsDeclarationSemanticsValidator { final PbsAst.TypeRef returnType, final PbsAst.TypeRef resultErrorType, final Span span, + final boolean allowSelf, final PbsNamespaceBinder binder, final PbsDeclarationRuleValidator rules) { - rules.validateCallableHeader(callableName, parameters, returnKind, returnType, resultErrorType, span); + rules.validateCallableHeader(callableName, parameters, returnKind, returnType, resultErrorType, span, allowSelf); binder.registerCallable( scope, callableName, 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 d0a837f4..d0170b8f 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 @@ -97,8 +97,15 @@ final class PbsFlowExpressionAnalyzer { if (expression instanceof PbsAst.UnitExpr) { return ExprResult.type(TypeView.unit()); } - if (expression instanceof PbsAst.ThisExpr) { - return ExprResult.type(receiverType == null ? TypeView.unknown() : receiverType); + if (expression instanceof PbsAst.ThisExpr thisExpr) { + if (receiverType == null) { + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, + PbsSemanticsErrors.E_SEM_INVALID_THIS_CONTEXT.name(), + "Invalid 'this' usage outside struct/service methods and constructors", + thisExpr.span()); + return ExprResult.type(TypeView.unknown()); + } + return ExprResult.type(receiverType); } if (expression instanceof PbsAst.IdentifierExpr identifierExpr) { final var localType = scope.resolve(identifierExpr.name()); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsSemanticsErrors.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsSemanticsErrors.java index d45e6de7..69ba9c00 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsSemanticsErrors.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsSemanticsErrors.java @@ -18,6 +18,8 @@ public enum PbsSemanticsErrors { E_SEM_APPLY_AMBIGUOUS_OVERLOAD, E_SEM_BARE_METHOD_EXTRACTION, E_SEM_INVALID_MEMBER_ACCESS, + E_SEM_INVALID_THIS_CONTEXT, + E_SEM_INVALID_SELF_CONTEXT, E_SEM_IF_NON_BOOL_CONDITION, E_SEM_IF_BRANCH_TYPE_MISMATCH, E_SEM_SWITCH_SELECTOR_INVALID, 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 c12781f6..c558df36 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 @@ -13,6 +13,7 @@ final class PbsTypeSurfaceSemanticsValidator { static void validateTypeSurfaceRecursive( final List roots, final String ownerDescription, + final boolean allowSelf, final DiagnosticSink diagnostics) { final var pending = new ArrayList<>(roots); while (!pending.isEmpty()) { @@ -37,6 +38,14 @@ final class PbsTypeSurfaceSemanticsValidator { pending.add(field.typeRef()); } } + case SELF -> { + if (!allowSelf) { + p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, + PbsSemanticsErrors.E_SEM_INVALID_SELF_CONTEXT.name(), + "Invalid 'Self' type usage in %s".formatted(ownerDescription), + typeRef.span()); + } + } default -> { // No recursive children. } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsSemanticsControlFlowTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsSemanticsControlFlowTest.java index 6b71d9e4..36559c9e 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsSemanticsControlFlowTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsSemanticsControlFlowTest.java @@ -6,6 +6,7 @@ import p.studio.compiler.source.diagnostics.DiagnosticSink; import p.studio.compiler.source.identifiers.FileId; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; class PbsSemanticsControlFlowTest { @@ -82,4 +83,44 @@ class PbsSemanticsControlFlowTest { .count(); assertEquals(1, duplicateWildcardCount); } + + @Test + void shouldAllowThisInsideStructServiceAndCtorBodies() { + final var source = """ + declare struct Box(v: int) { + fn read() -> int { return this.v; } + ctor make(seed: int) { this.v = seed; } + } + + declare service Game { + fn ping() -> int { this; return 0; } + } + + fn ok() -> int { return 0; } + """; + final var diagnostics = DiagnosticSink.empty(); + + new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics); + + assertFalse(diagnostics.stream().anyMatch(d -> + d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_THIS_CONTEXT.name()))); + } + + @Test + void shouldRejectThisOutsideReceiverContexts() { + final var source = """ + fn bad() -> int { + this; + return 0; + } + """; + final var diagnostics = DiagnosticSink.empty(); + + new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics); + + final var invalidThisCount = diagnostics.stream() + .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_THIS_CONTEXT.name())) + .count(); + assertEquals(1, invalidThisCount); + } } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsSemanticsDeclarationsTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsSemanticsDeclarationsTest.java index 6af060cc..817df3ee 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsSemanticsDeclarationsTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/semantics/PbsSemanticsDeclarationsTest.java @@ -5,6 +5,7 @@ import p.studio.compiler.pbs.PbsFrontendCompiler; import p.studio.compiler.source.diagnostics.DiagnosticSink; import p.studio.compiler.source.identifiers.FileId; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -144,4 +145,40 @@ class PbsSemanticsDeclarationsTest { assertTrue(diagnostics.stream().anyMatch(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_DECLARATION.name()))); } + + @Test + void shouldAllowSelfInStructServiceMethodsAndCtors() { + final var source = """ + declare struct Box(v: int) { + fn merge(other: Self) -> Self { return this; } + ctor make(copy: Self) { this.v = copy.v; } + } + + declare service Game { + fn mirror(current: Self) -> int { return 0; } + } + """; + final var diagnostics = DiagnosticSink.empty(); + + new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics); + + assertFalse(diagnostics.stream().anyMatch(d -> + d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_SELF_CONTEXT.name()))); + } + + @Test + void shouldRejectSelfOutsideAllowedDeclarationContexts() { + final var source = """ + fn bad(v: Self) -> int { return 0; } + declare const GLOBAL: Self = 1; + """; + final var diagnostics = DiagnosticSink.empty(); + + new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics); + + final var invalidSelfCount = diagnostics.stream() + .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_SELF_CONTEXT.name())) + .count(); + assertEquals(2, invalidSelfCount); + } }