implements PR014
This commit is contained in:
parent
c0bccba092
commit
4f32913577
@ -416,7 +416,14 @@ public final class PbsAst {
|
|||||||
Span span();
|
Span span();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum WildcardSwitchKind {
|
||||||
|
DEFAULT,
|
||||||
|
UNDERSCORE,
|
||||||
|
RECOVERY
|
||||||
|
}
|
||||||
|
|
||||||
public record WildcardSwitchPattern(
|
public record WildcardSwitchPattern(
|
||||||
|
WildcardSwitchKind kind,
|
||||||
Span span) implements SwitchPattern {
|
Span span) implements SwitchPattern {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -522,12 +522,16 @@ final class PbsExprParser {
|
|||||||
|
|
||||||
private PbsAst.SwitchPattern parseSwitchPattern() {
|
private PbsAst.SwitchPattern parseSwitchPattern() {
|
||||||
if (cursor.match(PbsTokenKind.DEFAULT)) {
|
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())) {
|
if (cursor.check(PbsTokenKind.IDENTIFIER) && "_".equals(cursor.peek().lexeme())) {
|
||||||
final var wildcard = cursor.advance();
|
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)
|
if (cursor.check(PbsTokenKind.IDENTIFIER)
|
||||||
@ -545,7 +549,9 @@ final class PbsExprParser {
|
|||||||
final var token = cursor.peek();
|
final var token = cursor.peek();
|
||||||
report(token, ParseErrors.E_PARSE_INVALID_SWITCH_FORM, "Invalid switch pattern");
|
report(token, ParseErrors.E_PARSE_INVALID_SWITCH_FORM, "Invalid switch pattern");
|
||||||
cursor.advance();
|
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() {
|
private PbsAst.Expression parseLiteralPatternExpression() {
|
||||||
|
|||||||
@ -850,18 +850,38 @@ final class PbsFlowExpressionAnalyzer {
|
|||||||
|
|
||||||
final var seenPatterns = new HashSet<String>();
|
final var seenPatterns = new HashSet<String>();
|
||||||
boolean hasWildcard = false;
|
boolean hasWildcard = false;
|
||||||
|
PbsAst.WildcardSwitchKind wildcardKind = null;
|
||||||
|
int wildcardCount = 0;
|
||||||
TypeView armType = null;
|
TypeView armType = null;
|
||||||
|
|
||||||
for (final var arm : switchExpr.arms()) {
|
for (final var arm : switchExpr.arms()) {
|
||||||
final var patternKey = switchPatternKey(arm.pattern());
|
if (arm.pattern() instanceof PbsAst.WildcardSwitchPattern wildcardPattern) {
|
||||||
if (arm.pattern() instanceof PbsAst.WildcardSwitchPattern) {
|
|
||||||
hasWildcard = true;
|
hasWildcard = true;
|
||||||
} else if (!seenPatterns.add(patternKey)) {
|
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,
|
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
||||||
PbsSemanticsErrors.E_SEM_SWITCH_DUPLICATE_PATTERN.name(),
|
PbsSemanticsErrors.E_SEM_SWITCH_DUPLICATE_PATTERN.name(),
|
||||||
"Duplicate switch pattern",
|
"Duplicate switch pattern",
|
||||||
arm.pattern().span());
|
arm.pattern().span());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final var patternType = switchPatternType(arm.pattern(), model, diagnostics);
|
final var patternType = switchPatternType(arm.pattern(), model, diagnostics);
|
||||||
if (patternType != null && !typeOps.compatible(patternType, selector)) {
|
if (patternType != null && !typeOps.compatible(patternType, selector)) {
|
||||||
|
|||||||
@ -48,7 +48,7 @@ class PbsExprParserTest {
|
|||||||
final var source = """
|
final var source = """
|
||||||
fn flow(cond: bool, state: int) -> int {
|
fn flow(cond: bool, state: int) -> int {
|
||||||
let a: int = if cond { 1 } else { 2 };
|
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 } };
|
let c: int = handle run apply () { Err.fail -> Other.fail, _ -> { 1 } };
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -69,7 +69,15 @@ class PbsExprParserTest {
|
|||||||
|
|
||||||
final var switchExpr = assertInstanceOf(PbsAst.SwitchExpr.class,
|
final var switchExpr = assertInstanceOf(PbsAst.SwitchExpr.class,
|
||||||
assertInstanceOf(PbsAst.LetStatement.class, body.get(1)).initializer());
|
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());
|
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,
|
final var handle = assertInstanceOf(PbsAst.HandleExpr.class,
|
||||||
assertInstanceOf(PbsAst.LetStatement.class, body.get(2)).initializer());
|
assertInstanceOf(PbsAst.LetStatement.class, body.get(2)).initializer());
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import p.studio.compiler.pbs.PbsFrontendCompiler;
|
|||||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
import p.studio.compiler.source.identifiers.FileId;
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
class PbsSemanticsControlFlowTest {
|
class PbsSemanticsControlFlowTest {
|
||||||
@ -39,4 +40,46 @@ class PbsSemanticsControlFlowTest {
|
|||||||
assertTrue(diagnostics.stream().anyMatch(d ->
|
assertTrue(diagnostics.stream().anyMatch(d ->
|
||||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_BARE_METHOD_EXTRACTION.name())));
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user