implements PR015

This commit is contained in:
bQUARKz 2026-03-05 19:09:06 +00:00
parent 4f32913577
commit 8d8c3dc180
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
10 changed files with 132 additions and 141 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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<PbsAst.TypeRef> 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);
}
}

View File

@ -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,

View File

@ -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());

View File

@ -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,

View File

@ -13,6 +13,7 @@ final class PbsTypeSurfaceSemanticsValidator {
static void validateTypeSurfaceRecursive(
final List<PbsAst.TypeRef> 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.
}

View File

@ -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);
}
}

View File

@ -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);
}
}