implements PLN-0007

This commit is contained in:
bQUARKz 2026-03-27 17:24:36 +00:00
parent 4a6cc35989
commit d28916578b
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
14 changed files with 357 additions and 12 deletions

View File

@ -4,6 +4,6 @@
{"type":"discussion","id":"DSC-0003","status":"done","ticket":"packer-docs-import","title":"Import docs/packer into discussion-framework artifacts","created_at":"2026-03-26","updated_at":"2026-03-26","tags":["packer","migration","discussion-framework","docs-import"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0009","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0009-mental-model-packer-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0010","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0010-asset-identity-and-runtime-contract-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0011","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0011-foundations-workspace-runtime-and-build-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0012","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0012-runtime-ownership-and-studio-boundary-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0013","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0013-metadata-convergence-and-runtime-sink-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0014","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0014-pack-wizard-summary-validation-and-pack-execution-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0015","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0015-tile-bank-packing-contract-legacy.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"},{"id":"LSN-0017","file":"discussion/lessons/DSC-0003-packer-docs-import/LSN-0017-packer-docs-import-pattern.md","status":"done","created_at":"2026-03-26","updated_at":"2026-03-26"}]}
{"type":"discussion","id":"DSC-0004","status":"open","ticket":"tilemap-and-metatile-runtime-binary-layout","title":"Tilemap and Metatile Runtime Binary Layout","created_at":"2026-03-26","updated_at":"2026-03-26","tags":["packer","legacy-import","tilemap","metatile","runtime-layout"],"agendas":[{"id":"AGD-0004","file":"AGD-0004-tilemap-and-metatile-runtime-binary-layout.md","status":"open","created_at":"2026-03-26","updated_at":"2026-03-26"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0005","status":"open","ticket":"variable-tile-bank-palette-serialization","title":"Variable Tile Bank Palette Serialization","created_at":"2026-03-26","updated_at":"2026-03-26","tags":["packer","legacy-import","tile-bank","palette-serialization","versioning"],"agendas":[{"id":"AGD-0005","file":"AGD-0005-variable-tile-bank-palette-serialization.md","status":"open","created_at":"2026-03-26","updated_at":"2026-03-26"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0006","status":"open","ticket":"pbs-game-facing-asset-refs-and-call-result-discard","title":"PBS Game-Facing Asset References and Ignored Call Result Lowering","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["compiler","pbs","ergonomics","lowering","runtime","asset-identity","expression-statements"],"agendas":[{"id":"AGD-0006","file":"AGD-0006-pbs-game-facing-asset-refs-and-call-result-discard.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[{"id":"DEC-0005","file":"DEC-0005-pbs-asset-address-surface-and-be-lowering.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27","ref_agenda":"AGD-0006"},{"id":"DEC-0006","file":"DEC-0006-pbs-ignored-values-lowering-and-warning.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27","ref_agenda":"AGD-0006"}],"plans":[{"id":"PLN-0005","file":"PLN-0005-pbs-asset-address-surface-spec-propagation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0005"]},{"id":"PLN-0006","file":"PLN-0006-pbs-asset-address-surface-implementation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0005"]},{"id":"PLN-0007","file":"PLN-0007-pbs-ignored-values-warning-implementation.md","status":"review","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0006"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0006","status":"open","ticket":"pbs-game-facing-asset-refs-and-call-result-discard","title":"PBS Game-Facing Asset References and Ignored Call Result Lowering","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["compiler","pbs","ergonomics","lowering","runtime","asset-identity","expression-statements"],"agendas":[{"id":"AGD-0006","file":"AGD-0006-pbs-game-facing-asset-refs-and-call-result-discard.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[{"id":"DEC-0005","file":"DEC-0005-pbs-asset-address-surface-and-be-lowering.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27","ref_agenda":"AGD-0006"},{"id":"DEC-0006","file":"DEC-0006-pbs-ignored-values-lowering-and-warning.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-03-27","ref_agenda":"AGD-0006"}],"plans":[{"id":"PLN-0005","file":"PLN-0005-pbs-asset-address-surface-spec-propagation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0005"]},{"id":"PLN-0006","file":"PLN-0006-pbs-asset-address-surface-implementation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0005"]},{"id":"PLN-0007","file":"PLN-0007-pbs-ignored-values-warning-implementation.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27","ref_decisions":["DEC-0006"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0007","status":"done","ticket":"pbs-learn-to-discussion-lessons-migration","title":"Migrate PBS Learn Documents into Discussion Lessons","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["compiler","pbs","migration","discussion-framework","lessons","learn-prune"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0018","file":"discussion/lessons/DSC-0007-pbs-learn-to-discussion-lessons-migration/LSN-0018-pbs-ast-and-parser-contract-legacy.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0019","file":"discussion/lessons/DSC-0007-pbs-learn-to-discussion-lessons-migration/LSN-0019-pbs-name-resolution-and-linking-legacy.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0020","file":"discussion/lessons/DSC-0007-pbs-learn-to-discussion-lessons-migration/LSN-0020-pbs-runtime-values-identity-memory-boundaries-legacy.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0021","file":"discussion/lessons/DSC-0007-pbs-learn-to-discussion-lessons-migration/LSN-0021-pbs-diagnostics-and-conformance-governance-legacy.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0022","file":"discussion/lessons/DSC-0007-pbs-learn-to-discussion-lessons-migration/LSN-0022-pbs-globals-lifecycle-and-published-entrypoint-legacy.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]}
{"type":"discussion","id":"DSC-0008","status":"done","ticket":"pbs-low-level-asset-manager-surface","title":"PBS Low-Level Asset Manager Surface for Runtime AssetManager","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["compiler","pbs","runtime","asset-manager","host-abi","stdlib","asset"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0023","file":"discussion/lessons/DSC-0008-pbs-low-level-asset-manager-surface/LSN-0023-lowassets-runtime-aligned-sdk-surface.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]}

View File

@ -2,9 +2,9 @@
id: PLN-0007
ticket: pbs-game-facing-asset-refs-and-call-result-discard
title: Implement DEC-0006 Ignored Values Lowering and Warning
status: review
status: done
created: 2026-03-27
completed:
completed: 2026-03-27
tags: [compiler, pbs, implementation, diagnostics, lowering, expression-statements, warnings]
---

View File

@ -203,6 +203,9 @@ At minimum, the PBS diagnostics baseline must cover:
- unresolved terminal asset reference,
- namespace-versus-terminal asset misuse,
- structurally invalid symbolic asset path admitted by source syntax but rejected by semantic surface rules.
10. ignored-value diagnostics for expression statements, including:
- a stable warning when a materialized value is produced and then ignored,
- and no warning for unit-like expression statements.
At minimum, host-admission diagnostics must cover missing or malformed host capability metadata and unknown or undeclared capability names.
@ -229,7 +232,9 @@ When failures exist, implementations must still preserve deterministic diagnosti
## 11. Cost Diagnostics and Warning Policy
Warnings are supported and recommended in v1, but they are not part of the minimum mandatory conformance baseline.
Warnings are supported in v1, and one warning is mandatory for PBS core: ignored materialized values in expression statements.
The mandatory warning identity for this rule is `W_SEM_IGNORED_VALUE`.
Tooling may surface qualitative cost facts as warnings or notes, especially for:

View File

@ -188,6 +188,12 @@ When a host-backed callsite also depends on backend-owned symbolic asset lowerin
- the preserved symbolic operand MUST remain attributable to a backend-provided `Addressable` identity;
- backend stages MUST be able to rewrite that symbolic operand into runtime-facing `asset_id` before the final low-level host path is emitted.
When an expression statement leaves a materialized runtime value on the stack:
- `IRBackend` lowering MUST emit explicit discard behavior rather than relying on implicit backend stack cleanup;
- for single-slot materialized values this discard is represented by `POP`;
- and the discard path MUST preserve stack-accounting validity for subsequent backend lowering stages.
### 12.4 VM-owned metadata obligations
VM-owned intrinsic callsites admitted at this boundary must preserve canonical intrinsic identity:

View File

@ -238,6 +238,9 @@ Rules:
- Symbolic asset references admitted by the frontend MUST resolve against the backend-provided `Addressable` surface rather than by direct packer queries.
- A valid symbolic asset reference MUST correspond to a terminal `Addressable` entry in the backend-provided surface.
- Intermediate `assets...` namespace nodes MUST NOT type-check as `Addressable` values.
- An expression statement MAY evaluate an expression that has a materialized value type.
- When an expression statement ignores a materialized value, PBS v1 MUST emit the ignored-value warning defined by the diagnostics specification.
- Unit-like expression statements MUST NOT emit the ignored-value warning.
### 3.4 Constant expressions

View File

@ -143,6 +143,7 @@ final class PbsExecutableBodyLowerer {
case PbsAst.WhileStatement whileStatement -> lowerWhileStatement(whileStatement, context);
case PbsAst.ExpressionStatement expressionStatement -> {
lowerExpression(expressionStatement.expression(), context);
discardIgnoredValueSlots(expressionStatement.expression(), context);
yield false;
}
case PbsAst.BreakStatement breakStatement -> lowerBreakStatement(breakStatement, context);
@ -426,6 +427,21 @@ final class PbsExecutableBodyLowerer {
}
}
private void discardIgnoredValueSlots(
final PbsAst.Expression expression,
final PbsExecutableLoweringContext context) {
final var slots = materializedValueSlots(expression, context);
for (int i = 0; i < slots; i++) {
context.instructions().add(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.POP,
"",
null,
null,
null,
expression == null ? p.studio.compiler.source.Span.none() : expression.span()));
}
}
private void lowerMemberExpression(
final PbsAst.MemberExpr memberExpr,
final PbsExecutableLoweringContext context) {
@ -508,6 +524,92 @@ final class PbsExecutableBodyLowerer {
return receiverPath + "." + memberExpr.memberName();
}
private int materializedValueSlots(
final PbsAst.Expression expression,
final PbsExecutableLoweringContext context) {
if (expression == null) {
return 0;
}
return switch (expression) {
case PbsAst.CallExpr callExpr -> callsiteEmitter.returnSlotsOf(callExpr, context);
case PbsAst.ApplyExpr ignored -> 0;
case PbsAst.BinaryExpr ignored -> 1;
case PbsAst.UnaryExpr ignored -> 1;
case PbsAst.ElseExpr elseExpr ->
Math.max(
materializedValueSlots(elseExpr.optionalExpression(), context),
materializedValueSlots(elseExpr.fallbackExpression(), context));
case PbsAst.IfExpr ifExpr ->
Math.max(
blockValueSlots(ifExpr.thenBlock(), context),
materializedValueSlots(ifExpr.elseExpression(), context));
case PbsAst.SwitchExpr ignored -> 0;
case PbsAst.HandleExpr ignored -> 0;
case PbsAst.AsExpr asExpr -> materializedValueSlots(asExpr.expression(), context);
case PbsAst.MemberExpr memberExpr -> {
final var assetReference = flattenAssetReference(memberExpr);
if (assetReference != null && context.resolveAssetId(assetReference) != null) {
yield 1;
}
yield materializedValueSlots(memberExpr.receiver(), context);
}
case PbsAst.PropagateExpr propagateExpr -> materializedValueSlots(propagateExpr.expression(), context);
case PbsAst.GroupExpr groupExpr -> materializedValueSlots(groupExpr.expression(), context);
case PbsAst.NewExpr newExpr -> expressionListValueSlots(newExpr.arguments(), context);
case PbsAst.BindExpr bindExpr -> materializedValueSlots(bindExpr.contextExpression(), context);
case PbsAst.SomeExpr someExpr -> materializedValueSlots(someExpr.value(), context);
case PbsAst.OkExpr okExpr -> materializedValueSlots(okExpr.value(), context);
case PbsAst.TupleExpr tupleExpr -> {
var slots = 0;
for (final var item : tupleExpr.items()) {
slots += materializedValueSlots(item.expression(), context);
}
yield slots;
}
case PbsAst.BlockExpr blockExpr -> blockValueSlots(blockExpr.block(), context);
case PbsAst.IdentifierExpr identifierExpr -> producesIdentifierValue(identifierExpr, context) ? 1 : 0;
case PbsAst.IntLiteralExpr ignored -> 1;
case PbsAst.StringLiteralExpr ignoredString -> 1;
case PbsAst.BoolLiteralExpr ignoredBool -> 1;
case PbsAst.FloatLiteralExpr ignoredFloat -> 0;
case PbsAst.BoundedLiteralExpr ignoredBounded -> 0;
case PbsAst.ThisExpr ignoredThis -> 0;
case PbsAst.NoneExpr ignoredNone -> 0;
case PbsAst.ErrExpr ignoredErr -> 0;
case PbsAst.UnitExpr ignoredUnit -> 0;
};
}
private int blockValueSlots(
final PbsAst.Block block,
final PbsExecutableLoweringContext context) {
if (block == null || block.tailExpression() == null) {
return 0;
}
return materializedValueSlots(block.tailExpression(), context);
}
private int expressionListValueSlots(
final ReadOnlyList<PbsAst.Expression> expressions,
final PbsExecutableLoweringContext context) {
var slots = 0;
for (final var expression : expressions) {
slots += materializedValueSlots(expression, context);
}
return slots;
}
private boolean producesIdentifierValue(
final PbsAst.IdentifierExpr identifierExpr,
final PbsExecutableLoweringContext context) {
if ("assets".equals(identifierExpr.name())) {
return false;
}
return context.resolveLocalSlot(identifierExpr.name()) != null
|| context.resolveGlobalSlot(identifierExpr.name()) != null
|| context.resolveConstDecl(identifierExpr.name()) != null;
}
private void lowerConstExpression(
final PbsAst.Expression expression,
final PbsExecutableLoweringContext context) {

View File

@ -16,6 +16,29 @@ import java.util.Map;
import static p.studio.compiler.pbs.lowering.PbsExecutableLoweringModels.*;
final class PbsExecutableCallsiteEmitter {
int returnSlotsOf(
final PbsAst.CallExpr callExpr,
final PbsExecutableLoweringContext context) {
final var calleeIdentity = resolveCalleeIdentity(callExpr.callee(), context.nameTable());
if (calleeIdentity == null) {
return 0;
}
final var candidates = resolveCallsiteCandidates(callExpr, calleeIdentity, context);
final var resolvedCategory = resolveCallsiteCategory(candidates, callExpr, context);
if (resolvedCategory == null) {
return 0;
}
return switch (resolvedCategory) {
case HOST -> candidates.hostCandidates().getFirst().retSlots();
case INTRINSIC -> candidates.intrinsicCandidates().getFirst().retSlots();
case CALLABLE -> {
if (candidates.callableCandidates().size() != 1) {
yield 0;
}
yield context.returnSlotsByCallableId().getOrDefault(candidates.callableCandidates().getFirst(), 0);
}
};
}
void emitCallsite(
final PbsAst.CallExpr callExpr,

View File

@ -172,7 +172,7 @@ final class PbsFlowStatementAnalyzer {
return;
}
if (statement instanceof PbsAst.ExpressionStatement expressionStatement) {
expressionAnalyzer.analyzeExpression(
final var expressionType = expressionAnalyzer.analyzeExpression(
expressionStatement.expression(),
scope,
null,
@ -183,7 +183,13 @@ final class PbsFlowStatementAnalyzer {
diagnostics,
ExprUse.VALUE,
false,
this::analyzeNestedBlock);
this::analyzeNestedBlock).type();
if (typeOps.isMaterializedValue(expressionType)) {
Diagnostics.warning(diagnostics,
PbsSemanticsErrors.W_SEM_IGNORED_VALUE.name(),
"Ignored materialized value in expression statement",
expressionStatement.span());
}
return;
}
if (statement instanceof PbsAst.AssignStatement assignStatement) {

View File

@ -139,6 +139,16 @@ final class PbsFlowTypeOps {
return type.kind() == Kind.STR || type.kind() == Kind.UNKNOWN;
}
boolean isMaterializedValue(final TypeView type) {
if (type == null) {
return false;
}
return switch (type.kind()) {
case UNKNOWN, UNIT, ASSET_NAMESPACE -> false;
default -> true;
};
}
private boolean tupleCompatible(final TypeView actual, final TypeView expected) {
if (actual.tupleFields().size() != expected.tupleFields().size()) {
return false;

View File

@ -69,4 +69,5 @@ public enum PbsSemanticsErrors {
E_SEM_HANDLE_ERROR_MISMATCH,
E_SEM_HANDLE_ARM_TERMINAL_INVALID,
E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE,
W_SEM_IGNORED_VALUE,
}

View File

@ -10,6 +10,7 @@ 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.diagnostics.Severity;
import p.studio.compiler.source.identifiers.FileId;
import p.studio.utilities.structures.ReadOnlyList;
@ -52,6 +53,28 @@ class PbsDiagnosticsContractTest {
assertEquals(DiagnosticPhase.STATIC_SEMANTICS, diagnostic.getPhase());
}
@Test
void shouldTagIgnoredValueWarningsWithStaticSemanticsPhase() {
final var source = """
fn pure() -> int { return 1; }
fn frame() -> void {
pure();
return;
}
""";
final var diagnostics = DiagnosticSink.empty();
new PbsFrontendCompiler().compileFile(new FileId(11), source, diagnostics);
final var diagnostic = diagnostics.stream()
.filter(d -> d.getCode().equals(PbsSemanticsErrors.W_SEM_IGNORED_VALUE.name()))
.findFirst()
.orElseThrow();
assertEquals(DiagnosticPhase.STATIC_SEMANTICS, diagnostic.getPhase());
assertEquals(Severity.Warning, diagnostic.getSeverity());
assertEquals(diagnostic.getCode(), diagnostic.getTemplateId());
}
@Test
void shouldTagLinkingDiagnosticsAndExposeRelatedSpanForDuplicateBarrelEntry() {
final var diagnostics = DiagnosticSink.empty();

View File

@ -288,13 +288,17 @@ class PbsFrontendCompilerTest {
final var compiler = new PbsFrontendCompiler();
final var fileBackend = compiler.compileFile(new FileId(99), source, diagnostics);
assertTrue(diagnostics.isEmpty(), "Valid program should not report diagnostics");
assertEquals(0, diagnostics.errorCount(), "Valid program should not report errors");
assertEquals(2, diagnostics.warningCount());
final var executableA = fileBackend.executableFunctions().stream()
.filter(fn -> fn.callableName().equals("a"))
.findFirst()
.orElseThrow();
assertEquals("b", executableA.instructions().get(0).calleeCallableName());
assertEquals("c", executableA.instructions().get(1).calleeCallableName());
final var callNames = executableA.instructions().stream()
.filter(i -> i.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_FUNC)
.map(p.studio.compiler.models.IRBackendExecutableFunction.Instruction::calleeCallableName)
.toList();
assertEquals(java.util.List.of("b", "c"), callNames);
}
@Test
@ -312,7 +316,8 @@ class PbsFrontendCompilerTest {
final var compiler = new PbsFrontendCompiler();
final var fileBackend = compiler.compileFile(new FileId(100), source, diagnostics);
assertTrue(diagnostics.isEmpty(), "Valid program should not report diagnostics");
assertEquals(0, diagnostics.errorCount(), "Valid program should not report errors");
assertEquals(1, diagnostics.warningCount());
final var executableMain = fileBackend.executableFunctions().stream()
.filter(fn -> fn.callableName().equals("main"))
.findFirst()
@ -512,6 +517,61 @@ class PbsFrontendCompilerTest {
assertEquals(0, fileBackend.executableFunctions().size());
}
@Test
void shouldWarnAndDiscardIgnoredMaterializedExpressionStatements() {
final var source = """
fn pure() -> int { return 1; }
fn frame() -> void {
pure();
1 + 2;
return;
}
""";
final var diagnostics = DiagnosticSink.empty();
final var compiler = new PbsFrontendCompiler();
final var fileBackend = compiler.compileFile(new FileId(14), source, diagnostics);
assertEquals(2, diagnostics.warningCount());
assertTrue(diagnostics.stream().anyMatch(d ->
d.getCode().equals(PbsSemanticsErrors.W_SEM_IGNORED_VALUE.name())));
final var executableFrame = fileBackend.executableFunctions().stream()
.filter(fn -> fn.callableName().equals("frame"))
.findFirst()
.orElseThrow();
assertTrue(executableFrame.instructions().stream().anyMatch(i ->
i.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_FUNC));
assertEquals(2, executableFrame.instructions().stream()
.filter(i -> i.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.POP)
.count());
}
@Test
void shouldNotWarnOrDiscardUnitExpressionStatements() {
final var source = """
fn log() -> void { return; }
fn frame() -> void {
log();
return;
}
""";
final var diagnostics = DiagnosticSink.empty();
final var compiler = new PbsFrontendCompiler();
final var fileBackend = compiler.compileFile(new FileId(15), source, diagnostics);
assertEquals(0, diagnostics.warningCount());
final var executableFrame = fileBackend.executableFunctions().stream()
.filter(fn -> fn.callableName().equals("frame"))
.findFirst()
.orElseThrow();
assertEquals(0, executableFrame.instructions().stream()
.filter(i -> i.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.POP)
.count());
}
@Test
void shouldRejectHostBindingWithoutCapabilityAtHostAdmission() {
final var source = """

View File

@ -0,0 +1,48 @@
package p.studio.compiler.pbs.semantics;
import org.junit.jupiter.api.Test;
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.assertTrue;
class PbsIgnoredValueSemanticsTest {
@Test
void shouldWarnWhenExpressionStatementIgnoresMaterializedValue() {
final var source = """
fn pure() -> int { return 1; }
fn frame() -> void {
pure();
return;
}
""";
final var diagnostics = DiagnosticSink.empty();
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);
assertEquals(1, diagnostics.warningCount());
assertTrue(diagnostics.stream().anyMatch(d ->
d.getCode().equals(PbsSemanticsErrors.W_SEM_IGNORED_VALUE.name())));
}
@Test
void shouldNotWarnWhenExpressionStatementIsUnitLike() {
final var source = """
fn log() -> void { return; }
fn frame() -> void {
log();
return;
}
""";
final var diagnostics = DiagnosticSink.empty();
new PbsFrontendCompiler().compileFile(new FileId(1), source, diagnostics);
assertEquals(0, diagnostics.warningCount());
}
}

View File

@ -87,7 +87,8 @@ class PBSFrontendPhaseServiceTest {
LogAggregator.empty(),
BuildingIssueSink.empty());
assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
assertEquals(0, diagnostics.errorCount(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
assertEquals(0, diagnostics.warningCount());
final var frameFn = irBackend.getExecutableFunctions().stream()
.filter(function -> "frame".equals(function.callableName()))
.findFirst()
@ -97,6 +98,62 @@ class PBSFrontendPhaseServiceTest {
&& "91".equals(instruction.label())));
}
@Test
void shouldKeepWarningsNonBlockingWhileLoweringExplicitDiscard() throws IOException {
final var projectRoot = tempDir.resolve("project-ignored-values");
final var sourceRoot = projectRoot.resolve("src");
final var modulePath = sourceRoot.resolve("main");
Files.createDirectories(modulePath);
final var sourceFile = modulePath.resolve("source.pbs");
final var modBarrel = modulePath.resolve("mod.barrel");
Files.writeString(sourceFile, """
fn pure() -> int { return 1; }
[Frame]
fn frame() -> void {
pure();
return;
}
""");
Files.writeString(modBarrel, "pub fn frame() -> void;");
final var projectTable = new ProjectTable();
final var fileTable = new FileTable(1);
final var projectId = projectTable.register(ProjectDescriptor.builder()
.rootPath(projectRoot)
.name("core")
.version("1.0.0")
.sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
.build());
registerFile(projectId, projectRoot, sourceFile, fileTable);
registerFile(projectId, projectRoot, modBarrel, fileTable);
final var ctx = new FrontendPhaseContext(
projectTable,
fileTable,
new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
final var diagnostics = DiagnosticSink.empty();
final var irBackend = new PBSFrontendPhaseService().compile(
ctx,
diagnostics,
LogAggregator.empty(),
BuildingIssueSink.empty());
assertEquals(1, diagnostics.warningCount());
assertEquals(0, diagnostics.errorCount());
assertTrue(diagnostics.stream().anyMatch(d ->
d.getCode().equals(PbsSemanticsErrors.W_SEM_IGNORED_VALUE.name())));
final var frameFn = irBackend.getExecutableFunctions().stream()
.filter(function -> "frame".equals(function.callableName()))
.findFirst()
.orElseThrow();
assertTrue(frameFn.instructions().stream().anyMatch(instruction ->
instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.POP));
}
@Test
void shouldReportInvalidBarrelFilename() throws IOException {
final var projectRoot = tempDir.resolve("project");
@ -259,7 +316,8 @@ class PBSFrontendPhaseServiceTest {
LogAggregator.empty(),
BuildingIssueSink.empty());
assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
assertEquals(0, diagnostics.errorCount(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
assertEquals(1, diagnostics.warningCount());
final var targetFn = irBackend.getExecutableFunctions().stream()
.filter(function -> "target".equals(function.callableName()))