implements PR015
This commit is contained in:
parent
4f32913577
commit
8d8c3dc180
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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.
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user