implements PR014

This commit is contained in:
bQUARKz 2026-03-05 18:55:12 +00:00
parent c0bccba092
commit 4f32913577
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
5 changed files with 95 additions and 11 deletions

View File

@ -416,7 +416,14 @@ public final class PbsAst {
Span span();
}
public enum WildcardSwitchKind {
DEFAULT,
UNDERSCORE,
RECOVERY
}
public record WildcardSwitchPattern(
WildcardSwitchKind kind,
Span span) implements SwitchPattern {
}

View File

@ -522,12 +522,16 @@ final class PbsExprParser {
private PbsAst.SwitchPattern parseSwitchPattern() {
if (cursor.match(PbsTokenKind.DEFAULT)) {
return new PbsAst.WildcardSwitchPattern(span(cursor.previous().start(), cursor.previous().end()));
return new PbsAst.WildcardSwitchPattern(
PbsAst.WildcardSwitchKind.DEFAULT,
span(cursor.previous().start(), cursor.previous().end()));
}
if (cursor.check(PbsTokenKind.IDENTIFIER) && "_".equals(cursor.peek().lexeme())) {
final var wildcard = cursor.advance();
return new PbsAst.WildcardSwitchPattern(span(wildcard.start(), wildcard.end()));
return new PbsAst.WildcardSwitchPattern(
PbsAst.WildcardSwitchKind.UNDERSCORE,
span(wildcard.start(), wildcard.end()));
}
if (cursor.check(PbsTokenKind.IDENTIFIER)
@ -545,7 +549,9 @@ final class PbsExprParser {
final var token = cursor.peek();
report(token, ParseErrors.E_PARSE_INVALID_SWITCH_FORM, "Invalid switch pattern");
cursor.advance();
return new PbsAst.WildcardSwitchPattern(span(token.start(), token.end()));
return new PbsAst.WildcardSwitchPattern(
PbsAst.WildcardSwitchKind.RECOVERY,
span(token.start(), token.end()));
}
private PbsAst.Expression parseLiteralPatternExpression() {

View File

@ -850,17 +850,37 @@ final class PbsFlowExpressionAnalyzer {
final var seenPatterns = new HashSet<String>();
boolean hasWildcard = false;
PbsAst.WildcardSwitchKind wildcardKind = null;
int wildcardCount = 0;
TypeView armType = null;
for (final var arm : switchExpr.arms()) {
final var patternKey = switchPatternKey(arm.pattern());
if (arm.pattern() instanceof PbsAst.WildcardSwitchPattern) {
if (arm.pattern() instanceof PbsAst.WildcardSwitchPattern wildcardPattern) {
hasWildcard = true;
} else if (!seenPatterns.add(patternKey)) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_SWITCH_DUPLICATE_PATTERN.name(),
"Duplicate switch pattern",
arm.pattern().span());
if (wildcardPattern.kind() != PbsAst.WildcardSwitchKind.RECOVERY) {
wildcardCount++;
if (wildcardCount == 1) {
wildcardKind = wildcardPattern.kind();
} else if (wildcardKind != wildcardPattern.kind()) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_SWITCH_DUPLICATE_PATTERN.name(),
"Switch wildcard arms cannot mix 'default' and '_'",
arm.pattern().span());
} else {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_SWITCH_DUPLICATE_PATTERN.name(),
"Duplicate switch wildcard pattern",
arm.pattern().span());
}
}
} else {
final var patternKey = switchPatternKey(arm.pattern());
if (!seenPatterns.add(patternKey)) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_SWITCH_DUPLICATE_PATTERN.name(),
"Duplicate switch pattern",
arm.pattern().span());
}
}
final var patternType = switchPatternType(arm.pattern(), model, diagnostics);

View File

@ -48,7 +48,7 @@ class PbsExprParserTest {
final var source = """
fn flow(cond: bool, state: int) -> int {
let a: int = if cond { 1 } else { 2 };
let b: int = switch state { default: { 0 } };
let b: int = switch state { default: { 0 }, _: { 1 } };
let c: int = handle run apply () { Err.fail -> Other.fail, _ -> { 1 } };
return 0;
}
@ -69,7 +69,15 @@ class PbsExprParserTest {
final var switchExpr = assertInstanceOf(PbsAst.SwitchExpr.class,
assertInstanceOf(PbsAst.LetStatement.class, body.get(1)).initializer());
assertEquals(2, switchExpr.arms().size());
final var defaultPattern = assertInstanceOf(PbsAst.WildcardSwitchPattern.class,
switchExpr.arms().getFirst().pattern());
assertEquals(PbsAst.WildcardSwitchKind.DEFAULT, defaultPattern.kind());
assertInstanceOf(PbsAst.IntLiteralExpr.class, switchExpr.arms().getFirst().block().tailExpression());
final var underscorePattern = assertInstanceOf(PbsAst.WildcardSwitchPattern.class,
switchExpr.arms().get(1).pattern());
assertEquals(PbsAst.WildcardSwitchKind.UNDERSCORE, underscorePattern.kind());
assertInstanceOf(PbsAst.IntLiteralExpr.class, switchExpr.arms().get(1).block().tailExpression());
final var handle = assertInstanceOf(PbsAst.HandleExpr.class,
assertInstanceOf(PbsAst.LetStatement.class, body.get(2)).initializer());

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.assertTrue;
class PbsSemanticsControlFlowTest {
@ -39,4 +40,46 @@ class PbsSemanticsControlFlowTest {
assertTrue(diagnostics.stream().anyMatch(d ->
d.getCode().equals(PbsSemanticsErrors.E_SEM_BARE_METHOD_EXTRACTION.name())));
}
@Test
void shouldRejectDuplicateSwitchWildcardArms() {
final var source = """
fn flow(state: int) -> int {
let value: int = switch state {
default: { 1 },
default: { 2 },
};
return value;
}
""";
final var diagnostics = DiagnosticSink.empty();
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);
final var duplicateWildcardCount = diagnostics.stream()
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_SWITCH_DUPLICATE_PATTERN.name()))
.count();
assertEquals(1, duplicateWildcardCount);
}
@Test
void shouldRejectMixedSwitchWildcardSpellings() {
final var source = """
fn flow(state: int) -> int {
let value: int = switch state {
default: { 1 },
_: { 2 },
};
return value;
}
""";
final var diagnostics = DiagnosticSink.empty();
new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);
final var duplicateWildcardCount = diagnostics.stream()
.filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_SWITCH_DUPLICATE_PATTERN.name()))
.count();
assertEquals(1, duplicateWildcardCount);
}
}