implements PR021
This commit is contained in:
parent
d2287a0a58
commit
d7b070ab68
@ -29,3 +29,4 @@ Cada documento e auto contido e inclui: `Briefing`, `Target`, `Method`, `Accepta
|
||||
20. `PR-018-pbs-result-flow-ok-err-handle-rules.md`
|
||||
21. `PR-019-pbs-non-unit-fallthrough-diagnostics.md`
|
||||
22. `PR-020-pbs-lowering-admission-gates.md`
|
||||
23. `PR-021-pbs-handle-aware-fallthrough-completion.md`
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
# PR-020 - PBS Lowering Admission Gates
|
||||
|
||||
## Briefing
|
||||
|
||||
Lowering currently proceeds even when syntax/static/linking diagnostics contain errors. This PR introduces explicit admission gates so invalid programs are rejected before IR lowering.
|
||||
|
||||
## Motivation
|
||||
|
||||
Lowering must not convert required rejections into accepted lowered artifacts.
|
||||
|
||||
## Target
|
||||
|
||||
- `PbsFrontendCompiler` and `PBSFrontendPhaseService` lowering flow.
|
||||
- Frontend-to-IR admission policy.
|
||||
|
||||
## Scope
|
||||
|
||||
- Block IR emission for source units with fatal diagnostics.
|
||||
- Preserve deterministic diagnostics already emitted by previous phases.
|
||||
- Keep successful paths unchanged.
|
||||
|
||||
## Method
|
||||
|
||||
- Introduce phase gate checks before `lowerFunctions` / file merge.
|
||||
- Ensure failed units are excluded from emitted IR.
|
||||
- Keep build issue adaptation behavior stable.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Files/modules with syntax/static/linking errors are not lowered.
|
||||
- No invalid callable appears in IR output after failed admission.
|
||||
- Valid files continue lowering unchanged.
|
||||
|
||||
## Tests
|
||||
|
||||
- Add tests proving failed parse/semantic/linking input does not emit IR functions.
|
||||
- Add tests proving clean input still emits expected IR.
|
||||
- Run frontend phase service and compiler suites.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Redefining diagnostics severity policy.
|
||||
- Introducing partial lowering recovery strategy.
|
||||
@ -180,9 +180,57 @@ final class PbsFlowBodyAnalyzer {
|
||||
if (expression instanceof PbsAst.SwitchExpr switchExpr) {
|
||||
return switchAlwaysReturns(switchExpr, model);
|
||||
}
|
||||
if (expression instanceof PbsAst.HandleExpr handleExpr) {
|
||||
return handleAlwaysReturns(handleExpr, model);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean handleAlwaysReturns(
|
||||
final PbsAst.HandleExpr handleExpr,
|
||||
final Model model) {
|
||||
if (expressionAlwaysReturns(handleExpr.value(), model)) {
|
||||
return true;
|
||||
}
|
||||
if (handleExpr.arms().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (final var arm : handleExpr.arms()) {
|
||||
if (!handleArmAlwaysReturns(arm, model)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean handleArmAlwaysReturns(
|
||||
final PbsAst.HandleArm arm,
|
||||
final Model model) {
|
||||
if (arm.remapTarget() != null) {
|
||||
return true;
|
||||
}
|
||||
if (arm.block() == null) {
|
||||
return false;
|
||||
}
|
||||
final var terminal = unwrapGroup(arm.block().tailExpression());
|
||||
if (terminal instanceof PbsAst.ErrExpr errExpr) {
|
||||
return isKnownErrorPath(errExpr.errorPath(), model);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isKnownErrorPath(
|
||||
final PbsAst.ErrorPath path,
|
||||
final Model model) {
|
||||
if (path == null || path.segments().size() != 2) {
|
||||
return false;
|
||||
}
|
||||
final var errorName = path.segments().getFirst();
|
||||
final var caseName = path.segments().get(1);
|
||||
final var errorCases = model.errors.get(errorName);
|
||||
return errorCases != null && errorCases.contains(caseName);
|
||||
}
|
||||
|
||||
private boolean switchAlwaysReturns(
|
||||
final PbsAst.SwitchExpr switchExpr,
|
||||
final Model model) {
|
||||
|
||||
@ -7,6 +7,7 @@ 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 PbsSemanticsFallthroughTest {
|
||||
|
||||
@ -110,4 +111,100 @@ class PbsSemanticsFallthroughTest {
|
||||
assertFalse(diagnostics.stream().anyMatch(d ->
|
||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_POSSIBLE_FALLTHROUGH_RESULT.name())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTreatTerminalHandleArmsAsCallableCompletion() {
|
||||
final var source = """
|
||||
declare enum Mode(Idle, Run);
|
||||
declare error InErr { A; B; }
|
||||
declare error OutErr { Retry; Abort; }
|
||||
|
||||
fn source(v: int) -> result<InErr> int {
|
||||
if v > 0 { return err(InErr.A); }
|
||||
return err(InErr.B);
|
||||
}
|
||||
|
||||
fn handleShortRemapTerminal(v: int) -> result<OutErr> int {
|
||||
handle source(v) {
|
||||
InErr.A -> OutErr.Retry,
|
||||
InErr.B -> OutErr.Abort,
|
||||
};
|
||||
}
|
||||
|
||||
fn handleBlockErrTerminal(v: int) -> result<OutErr> int {
|
||||
handle source(v) {
|
||||
InErr.A -> { err(OutErr.Retry) },
|
||||
InErr.B -> { err(OutErr.Abort) },
|
||||
};
|
||||
}
|
||||
|
||||
fn handleTerminalInIf(flag: bool, v: int) -> result<OutErr> int {
|
||||
if flag {
|
||||
handle source(v) {
|
||||
InErr.A -> OutErr.Retry,
|
||||
InErr.B -> OutErr.Abort,
|
||||
};
|
||||
} else {
|
||||
return err(OutErr.Abort);
|
||||
}
|
||||
}
|
||||
|
||||
fn handleTerminalInSwitch(mode: Mode, v: int) -> result<OutErr> int {
|
||||
switch mode {
|
||||
Mode.Idle: {
|
||||
handle source(v) {
|
||||
InErr.A -> { err(OutErr.Retry) },
|
||||
InErr.B -> { err(OutErr.Abort) },
|
||||
};
|
||||
},
|
||||
_: { return err(OutErr.Abort); },
|
||||
};
|
||||
}
|
||||
""";
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
|
||||
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);
|
||||
|
||||
assertFalse(diagnostics.stream().anyMatch(d ->
|
||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_POSSIBLE_FALLTHROUGH_RESULT.name())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepFallthroughWhenHandleArmRecoversWithOk() {
|
||||
final var source = """
|
||||
declare error InErr { A; B; }
|
||||
declare error OutErr { Retry; Abort; }
|
||||
|
||||
fn source(v: int) -> result<InErr> int {
|
||||
if v > 0 { return err(InErr.A); }
|
||||
return err(InErr.B);
|
||||
}
|
||||
|
||||
fn handleRecoveringTail(v: int) -> result<OutErr> int {
|
||||
handle source(v) {
|
||||
InErr.A -> { ok(1) },
|
||||
InErr.B -> { err(OutErr.Abort) },
|
||||
};
|
||||
}
|
||||
|
||||
fn handleRecoveringInIf(flag: bool, v: int) -> result<OutErr> int {
|
||||
if flag {
|
||||
handle source(v) {
|
||||
InErr.A -> { ok(1) },
|
||||
InErr.B -> { err(OutErr.Abort) },
|
||||
};
|
||||
} else {
|
||||
return err(OutErr.Abort);
|
||||
}
|
||||
}
|
||||
""";
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
|
||||
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);
|
||||
|
||||
final var resultFallthroughCount = diagnostics.stream()
|
||||
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_POSSIBLE_FALLTHROUGH_RESULT.name()))
|
||||
.count();
|
||||
assertTrue(resultFallthroughCount >= 2);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user