implements PR-12.4
This commit is contained in:
parent
0652c1ab28
commit
dd598b3989
@ -0,0 +1,351 @@
|
|||||||
|
package p.studio.compiler.pbs.semantics;
|
||||||
|
|
||||||
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
|
import p.studio.compiler.source.diagnostics.Diagnostics;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.ExprResult;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Kind;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.TypeView;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
final class PbsFlowControlExpressionAnalyzer {
|
||||||
|
@FunctionalInterface
|
||||||
|
interface ExpressionAnalyzerDelegate {
|
||||||
|
ExprResult analyze(PbsAst.Expression expression, PbsFlowExpressionContext context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface BlockAnalysisDelegate {
|
||||||
|
TypeView analyze(PbsAst.Block block, PbsFlowExpressionContext context, boolean valueContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final PbsFlowTypeOps typeOps;
|
||||||
|
private final ExpressionAnalyzerDelegate expressionAnalyzerDelegate;
|
||||||
|
private final BlockAnalysisDelegate blockAnalysisDelegate;
|
||||||
|
|
||||||
|
PbsFlowControlExpressionAnalyzer(
|
||||||
|
final PbsFlowTypeOps typeOps,
|
||||||
|
final ExpressionAnalyzerDelegate expressionAnalyzerDelegate,
|
||||||
|
final BlockAnalysisDelegate blockAnalysisDelegate) {
|
||||||
|
this.typeOps = typeOps;
|
||||||
|
this.expressionAnalyzerDelegate = expressionAnalyzerDelegate;
|
||||||
|
this.blockAnalysisDelegate = blockAnalysisDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeView analyzeSwitchExpression(
|
||||||
|
final PbsAst.SwitchExpr switchExpr,
|
||||||
|
final PbsFlowExpressionContext context) {
|
||||||
|
final var model = context.model();
|
||||||
|
final var diagnostics = context.diagnostics();
|
||||||
|
final var selector = analyzeValueExpression(switchExpr.selector(), context, null).type();
|
||||||
|
|
||||||
|
final var selectorComparable = typeOps.isScalarComparable(selector) || selector.kind() == Kind.ENUM;
|
||||||
|
if (!selectorComparable) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_SWITCH_SELECTOR_INVALID.name(),
|
||||||
|
"Switch selector type is not supported",
|
||||||
|
switchExpr.selector().span());
|
||||||
|
}
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
if (arm.pattern() instanceof PbsAst.WildcardSwitchPattern wildcardPattern) {
|
||||||
|
hasWildcard = true;
|
||||||
|
if (wildcardPattern.kind() != PbsAst.WildcardSwitchKind.RECOVERY) {
|
||||||
|
wildcardCount++;
|
||||||
|
if (wildcardCount == 1) {
|
||||||
|
wildcardKind = wildcardPattern.kind();
|
||||||
|
} else if (wildcardKind != wildcardPattern.kind()) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_SWITCH_DUPLICATE_PATTERN.name(),
|
||||||
|
"Switch wildcard arms cannot mix 'default' and '_'",
|
||||||
|
arm.pattern().span());
|
||||||
|
} else {
|
||||||
|
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)) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_SWITCH_DUPLICATE_PATTERN.name(),
|
||||||
|
"Duplicate switch pattern",
|
||||||
|
arm.pattern().span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final var patternType = switchPatternType(arm.pattern(), model, diagnostics);
|
||||||
|
if (patternType != null && !typeOps.compatible(patternType, selector)) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_SWITCH_PATTERN_TYPE_MISMATCH.name(),
|
||||||
|
"Switch pattern is not compatible with selector type",
|
||||||
|
arm.pattern().span());
|
||||||
|
}
|
||||||
|
|
||||||
|
final var currentArmType = blockAnalysisDelegate.analyze(arm.block(), context, true);
|
||||||
|
if (armType == null) {
|
||||||
|
armType = currentArmType;
|
||||||
|
} else if (!typeOps.compatible(currentArmType, armType)) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_SWITCH_ARM_TYPE_MISMATCH.name(),
|
||||||
|
"Switch arm block types must be compatible",
|
||||||
|
arm.span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.valueContext() && !hasWildcard) {
|
||||||
|
var exhaustive = false;
|
||||||
|
if (selector.kind() == Kind.ENUM) {
|
||||||
|
final var allCases = model.enums.get(selector.name());
|
||||||
|
if (allCases != null) {
|
||||||
|
final var covered = new HashSet<String>();
|
||||||
|
for (final var arm : switchExpr.arms()) {
|
||||||
|
if (arm.pattern() instanceof PbsAst.EnumCaseSwitchPattern enumCasePattern
|
||||||
|
&& enumCasePattern.path().segments().size() == 2
|
||||||
|
&& selector.name().equals(enumCasePattern.path().segments().getFirst())) {
|
||||||
|
covered.add(enumCasePattern.path().segments().get(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exhaustive = covered.containsAll(allCases);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!exhaustive) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_SWITCH_NON_EXHAUSTIVE.name(),
|
||||||
|
"Switch expression in value position must be exhaustive",
|
||||||
|
switchExpr.span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (armType == null) {
|
||||||
|
return TypeView.unit();
|
||||||
|
}
|
||||||
|
if (context.expectedType() != null && typeOps.compatible(armType, context.expectedType())) {
|
||||||
|
return context.expectedType();
|
||||||
|
}
|
||||||
|
return armType;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeView analyzeHandleExpression(
|
||||||
|
final PbsAst.HandleExpr handleExpr,
|
||||||
|
final PbsFlowExpressionContext context) {
|
||||||
|
final var model = context.model();
|
||||||
|
final var diagnostics = context.diagnostics();
|
||||||
|
final var resultErrorName = context.resultErrorName();
|
||||||
|
final var sourceType = analyzeValueExpression(handleExpr.value(), context, null).type();
|
||||||
|
if (sourceType.kind() != Kind.RESULT) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_HANDLE_NON_RESULT.name(),
|
||||||
|
"Handle requires result expression",
|
||||||
|
handleExpr.value().span());
|
||||||
|
return TypeView.unknown();
|
||||||
|
}
|
||||||
|
if (resultErrorName == null) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_HANDLE_ERROR_MISMATCH.name(),
|
||||||
|
"Handle requires enclosing callable with result return",
|
||||||
|
handleExpr.span());
|
||||||
|
}
|
||||||
|
|
||||||
|
final var sourceErrorName = sourceType.errorType() == null ? null : sourceType.errorType().name();
|
||||||
|
final var sourceCases = sourceErrorName == null ? Set.<String>of() : model.errors.getOrDefault(sourceErrorName, Set.of());
|
||||||
|
final var sourcePayloadType = sourceType.inner() == null ? TypeView.unknown() : sourceType.inner();
|
||||||
|
final var matchedCases = new HashSet<String>();
|
||||||
|
var hasWildcard = false;
|
||||||
|
|
||||||
|
for (final var arm : handleExpr.arms()) {
|
||||||
|
if (arm.pattern() instanceof PbsAst.WildcardHandlePattern) {
|
||||||
|
hasWildcard = true;
|
||||||
|
} else if (arm.pattern() instanceof PbsAst.ErrorPathHandlePattern errorPathHandlePattern) {
|
||||||
|
final var segments = errorPathHandlePattern.path().segments();
|
||||||
|
if (segments.size() == 2) {
|
||||||
|
final var errorName = segments.getFirst();
|
||||||
|
final var caseName = segments.get(1);
|
||||||
|
if (!errorName.equals(sourceErrorName) || !sourceCases.contains(caseName)) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_HANDLE_ERROR_MISMATCH.name(),
|
||||||
|
"Handle arm pattern does not match source result error type",
|
||||||
|
arm.pattern().span());
|
||||||
|
} else if (!matchedCases.add(caseName)) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_HANDLE_ERROR_MISMATCH.name(),
|
||||||
|
"Handle arm duplicates same error case pattern",
|
||||||
|
arm.pattern().span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arm.remapTarget() != null) {
|
||||||
|
if (!matchesTargetError(arm.remapTarget(), resultErrorName, model)) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_HANDLE_ERROR_MISMATCH.name(),
|
||||||
|
"Handle remap target must match enclosing callable result error type",
|
||||||
|
arm.remapTarget().span());
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
analyzeHandleBlockArm(arm, context, sourcePayloadType, model, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasWildcard && !sourceCases.isEmpty() && !matchedCases.containsAll(sourceCases)) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_HANDLE_ERROR_MISMATCH.name(),
|
||||||
|
"Handle mapping is not exhaustive for source result error cases",
|
||||||
|
handleExpr.span());
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourceType.inner() == null ? TypeView.unknown() : sourceType.inner();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void analyzeHandleBlockArm(
|
||||||
|
final PbsAst.HandleArm arm,
|
||||||
|
final PbsFlowExpressionContext context,
|
||||||
|
final TypeView sourcePayloadType,
|
||||||
|
final PbsFlowSemanticSupport.Model model,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var resultErrorName = context.resultErrorName();
|
||||||
|
if (arm.block() == null) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_HANDLE_ARM_TERMINAL_INVALID.name(),
|
||||||
|
"Handle block arm must terminate with 'ok(...)' or 'err(E.case)'",
|
||||||
|
arm.span());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var block = arm.block();
|
||||||
|
final var terminal = unwrapGroup(block.tailExpression());
|
||||||
|
if (terminal instanceof PbsAst.OkExpr okExpr) {
|
||||||
|
final var payloadBlock = new PbsAst.Block(block.statements(), okExpr.value(), block.span());
|
||||||
|
final var actualPayloadType = blockAnalysisDelegate.analyze(payloadBlock, context, true);
|
||||||
|
if (!typeOps.compatible(actualPayloadType, sourcePayloadType)) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_RESULT_OK_PAYLOAD_MISMATCH.name(),
|
||||||
|
"Handle arm 'ok(...)' payload is incompatible with source result payload type",
|
||||||
|
okExpr.value().span());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (terminal instanceof PbsAst.ErrExpr errExpr) {
|
||||||
|
final var statementsOnly = new PbsAst.Block(block.statements(), null, block.span());
|
||||||
|
blockAnalysisDelegate.analyze(statementsOnly, context, false);
|
||||||
|
if (resultErrorName != null && !matchesTargetError(errExpr.errorPath(), resultErrorName, model)) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_RESULT_ERROR_LABEL_INVALID.name(),
|
||||||
|
"Error label in handle arm 'err(...)' does not match enclosing result error type",
|
||||||
|
errExpr.errorPath().span());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block.tailExpression() == null) {
|
||||||
|
final var statementsOnly = new PbsAst.Block(block.statements(), null, block.span());
|
||||||
|
blockAnalysisDelegate.analyze(statementsOnly, context, false);
|
||||||
|
} else {
|
||||||
|
blockAnalysisDelegate.analyze(block, context, true);
|
||||||
|
}
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_HANDLE_ARM_TERMINAL_INVALID.name(),
|
||||||
|
"Handle block arm must terminate with 'ok(...)' or 'err(E.case)'",
|
||||||
|
block.span());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String switchPatternKey(final PbsAst.SwitchPattern pattern) {
|
||||||
|
if (pattern instanceof PbsAst.WildcardSwitchPattern) {
|
||||||
|
return "<wildcard>";
|
||||||
|
}
|
||||||
|
if (pattern instanceof PbsAst.EnumCaseSwitchPattern enumCaseSwitchPattern) {
|
||||||
|
return "enum:" + String.join(".", enumCaseSwitchPattern.path().segments().asList());
|
||||||
|
}
|
||||||
|
if (pattern instanceof PbsAst.LiteralSwitchPattern literalSwitchPattern) {
|
||||||
|
return "lit:" + literalSwitchPattern.literal().toString();
|
||||||
|
}
|
||||||
|
return "<unknown>";
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeView switchPatternType(
|
||||||
|
final PbsAst.SwitchPattern pattern,
|
||||||
|
final PbsFlowSemanticSupport.Model model,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
if (pattern instanceof PbsAst.WildcardSwitchPattern) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (pattern instanceof PbsAst.EnumCaseSwitchPattern enumCaseSwitchPattern) {
|
||||||
|
final var segments = enumCaseSwitchPattern.path().segments();
|
||||||
|
if (segments.size() != 2) {
|
||||||
|
return TypeView.unknown();
|
||||||
|
}
|
||||||
|
final var enumName = segments.getFirst();
|
||||||
|
final var caseName = segments.get(1);
|
||||||
|
final var enumCases = model.enums.get(enumName);
|
||||||
|
if (enumCases == null || !enumCases.contains(caseName)) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_SWITCH_PATTERN_TYPE_MISMATCH.name(),
|
||||||
|
"Enum case pattern '%s.%s' does not resolve".formatted(enumName, caseName),
|
||||||
|
enumCaseSwitchPattern.span());
|
||||||
|
return TypeView.unknown();
|
||||||
|
}
|
||||||
|
return TypeView.enumType(enumName);
|
||||||
|
}
|
||||||
|
if (pattern instanceof PbsAst.LiteralSwitchPattern literalSwitchPattern) {
|
||||||
|
final var literal = literalSwitchPattern.literal();
|
||||||
|
if (literal instanceof PbsAst.IntLiteralExpr || literal instanceof PbsAst.BoundedLiteralExpr) {
|
||||||
|
return TypeView.intType();
|
||||||
|
}
|
||||||
|
if (literal instanceof PbsAst.FloatLiteralExpr) {
|
||||||
|
return TypeView.floatType();
|
||||||
|
}
|
||||||
|
if (literal instanceof PbsAst.BoolLiteralExpr) {
|
||||||
|
return TypeView.bool();
|
||||||
|
}
|
||||||
|
if (literal instanceof PbsAst.StringLiteralExpr) {
|
||||||
|
return TypeView.str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TypeView.unknown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matchesTargetError(
|
||||||
|
final PbsAst.ErrorPath path,
|
||||||
|
final String resultErrorName,
|
||||||
|
final PbsFlowSemanticSupport.Model model) {
|
||||||
|
if (path == null || resultErrorName == null || path.segments().size() != 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final var errorName = path.segments().getFirst();
|
||||||
|
final var caseName = path.segments().get(1);
|
||||||
|
final var targetCases = model.errors.get(errorName);
|
||||||
|
return resultErrorName.equals(errorName) && targetCases != null && targetCases.contains(caseName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsAst.Expression unwrapGroup(final PbsAst.Expression expression) {
|
||||||
|
if (expression == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (expression instanceof PbsAst.GroupExpr groupExpr) {
|
||||||
|
return unwrapGroup(groupExpr.expression());
|
||||||
|
}
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExprResult analyzeValueExpression(
|
||||||
|
final PbsAst.Expression expression,
|
||||||
|
final PbsFlowExpressionContext context,
|
||||||
|
final TypeView expectedType) {
|
||||||
|
return expressionAnalyzerDelegate.analyze(
|
||||||
|
expression,
|
||||||
|
context.withExpectedType(expectedType)
|
||||||
|
.withUse(PbsFlowSemanticSupport.ExprUse.VALUE)
|
||||||
|
.withValueContext(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -34,6 +34,7 @@ final class PbsFlowExpressionAnalyzer {
|
|||||||
private final PbsFlowTypeOps typeOps;
|
private final PbsFlowTypeOps typeOps;
|
||||||
private final PbsFlowStructuralExpressionAnalyzer structuralExpressionAnalyzer;
|
private final PbsFlowStructuralExpressionAnalyzer structuralExpressionAnalyzer;
|
||||||
private final PbsFlowCallableResolutionAnalyzer callableResolutionAnalyzer;
|
private final PbsFlowCallableResolutionAnalyzer callableResolutionAnalyzer;
|
||||||
|
private final PbsFlowControlExpressionAnalyzer controlExpressionAnalyzer;
|
||||||
|
|
||||||
PbsFlowExpressionAnalyzer(final PbsFlowTypeOps typeOps) {
|
PbsFlowExpressionAnalyzer(final PbsFlowTypeOps typeOps) {
|
||||||
this.typeOps = typeOps;
|
this.typeOps = typeOps;
|
||||||
@ -44,6 +45,10 @@ final class PbsFlowExpressionAnalyzer {
|
|||||||
this.callableResolutionAnalyzer = new PbsFlowCallableResolutionAnalyzer(
|
this.callableResolutionAnalyzer = new PbsFlowCallableResolutionAnalyzer(
|
||||||
typeOps,
|
typeOps,
|
||||||
this::analyzeExpressionInternal);
|
this::analyzeExpressionInternal);
|
||||||
|
this.controlExpressionAnalyzer = new PbsFlowControlExpressionAnalyzer(
|
||||||
|
typeOps,
|
||||||
|
this::analyzeExpressionInternal,
|
||||||
|
this::analyzeBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult analyzeExpression(
|
ExprResult analyzeExpression(
|
||||||
@ -119,7 +124,7 @@ final class PbsFlowExpressionAnalyzer {
|
|||||||
return ExprResult.type(thenType);
|
return ExprResult.type(thenType);
|
||||||
}
|
}
|
||||||
if (expression instanceof PbsAst.SwitchExpr switchExpr) {
|
if (expression instanceof PbsAst.SwitchExpr switchExpr) {
|
||||||
return ExprResult.type(analyzeSwitchExpression(switchExpr, context));
|
return ExprResult.type(controlExpressionAnalyzer.analyzeSwitchExpression(switchExpr, context));
|
||||||
}
|
}
|
||||||
if (expression instanceof PbsAst.ElseExpr elseExpr) {
|
if (expression instanceof PbsAst.ElseExpr elseExpr) {
|
||||||
final var optional = analyzeValueExpression(elseExpr.optionalExpression(), context, null).type();
|
final var optional = analyzeValueExpression(elseExpr.optionalExpression(), context, null).type();
|
||||||
@ -161,7 +166,7 @@ final class PbsFlowExpressionAnalyzer {
|
|||||||
return ExprResult.type(source.inner());
|
return ExprResult.type(source.inner());
|
||||||
}
|
}
|
||||||
if (expression instanceof PbsAst.HandleExpr handleExpr) {
|
if (expression instanceof PbsAst.HandleExpr handleExpr) {
|
||||||
return ExprResult.type(analyzeHandleExpression(handleExpr, context));
|
return ExprResult.type(controlExpressionAnalyzer.analyzeHandleExpression(handleExpr, context));
|
||||||
}
|
}
|
||||||
if (expression instanceof PbsAst.BindExpr bindExpr) {
|
if (expression instanceof PbsAst.BindExpr bindExpr) {
|
||||||
return callableResolutionAnalyzer.analyzeBindExpression(bindExpr, context);
|
return callableResolutionAnalyzer.analyzeBindExpression(bindExpr, context);
|
||||||
@ -170,318 +175,6 @@ final class PbsFlowExpressionAnalyzer {
|
|||||||
return ExprResult.type(TypeView.unknown());
|
return ExprResult.type(TypeView.unknown());
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeView analyzeSwitchExpression(
|
|
||||||
final PbsAst.SwitchExpr switchExpr,
|
|
||||||
final PbsFlowExpressionContext context) {
|
|
||||||
final var model = context.model();
|
|
||||||
final var diagnostics = context.diagnostics();
|
|
||||||
final var selector = analyzeValueExpression(switchExpr.selector(), context, null).type();
|
|
||||||
|
|
||||||
final var selectorComparable = typeOps.isScalarComparable(selector) || selector.kind() == Kind.ENUM;
|
|
||||||
if (!selectorComparable) {
|
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_SWITCH_SELECTOR_INVALID.name(),
|
|
||||||
"Switch selector type is not supported",
|
|
||||||
switchExpr.selector().span());
|
|
||||||
}
|
|
||||||
|
|
||||||
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()) {
|
|
||||||
if (arm.pattern() instanceof PbsAst.WildcardSwitchPattern wildcardPattern) {
|
|
||||||
hasWildcard = true;
|
|
||||||
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);
|
|
||||||
if (patternType != null && !typeOps.compatible(patternType, selector)) {
|
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_SWITCH_PATTERN_TYPE_MISMATCH.name(),
|
|
||||||
"Switch pattern is not compatible with selector type",
|
|
||||||
arm.pattern().span());
|
|
||||||
}
|
|
||||||
|
|
||||||
final var currentArmType = analyzeBlock(arm.block(), context, true);
|
|
||||||
if (armType == null) {
|
|
||||||
armType = currentArmType;
|
|
||||||
} else if (!typeOps.compatible(currentArmType, armType)) {
|
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_SWITCH_ARM_TYPE_MISMATCH.name(),
|
|
||||||
"Switch arm block types must be compatible",
|
|
||||||
arm.span());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.valueContext() && !hasWildcard) {
|
|
||||||
var exhaustive = false;
|
|
||||||
if (selector.kind() == Kind.ENUM) {
|
|
||||||
final var allCases = model.enums.get(selector.name());
|
|
||||||
if (allCases != null) {
|
|
||||||
final var covered = new HashSet<String>();
|
|
||||||
for (final var arm : switchExpr.arms()) {
|
|
||||||
if (arm.pattern() instanceof PbsAst.EnumCaseSwitchPattern enumCasePattern
|
|
||||||
&& enumCasePattern.path().segments().size() == 2
|
|
||||||
&& selector.name().equals(enumCasePattern.path().segments().getFirst())) {
|
|
||||||
covered.add(enumCasePattern.path().segments().get(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exhaustive = covered.containsAll(allCases);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!exhaustive) {
|
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_SWITCH_NON_EXHAUSTIVE.name(),
|
|
||||||
"Switch expression in value position must be exhaustive",
|
|
||||||
switchExpr.span());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (armType == null) {
|
|
||||||
return TypeView.unit();
|
|
||||||
}
|
|
||||||
if (context.expectedType() != null && typeOps.compatible(armType, context.expectedType())) {
|
|
||||||
return context.expectedType();
|
|
||||||
}
|
|
||||||
return armType;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String switchPatternKey(final PbsAst.SwitchPattern pattern) {
|
|
||||||
if (pattern instanceof PbsAst.WildcardSwitchPattern) {
|
|
||||||
return "<wildcard>";
|
|
||||||
}
|
|
||||||
if (pattern instanceof PbsAst.EnumCaseSwitchPattern enumCaseSwitchPattern) {
|
|
||||||
return "enum:" + String.join(".", enumCaseSwitchPattern.path().segments().asList());
|
|
||||||
}
|
|
||||||
if (pattern instanceof PbsAst.LiteralSwitchPattern literalSwitchPattern) {
|
|
||||||
return "lit:" + literalSwitchPattern.literal().toString();
|
|
||||||
}
|
|
||||||
return "<unknown>";
|
|
||||||
}
|
|
||||||
|
|
||||||
private TypeView switchPatternType(
|
|
||||||
final PbsAst.SwitchPattern pattern,
|
|
||||||
final Model model,
|
|
||||||
final DiagnosticSink diagnostics) {
|
|
||||||
if (pattern instanceof PbsAst.WildcardSwitchPattern) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (pattern instanceof PbsAst.EnumCaseSwitchPattern enumCaseSwitchPattern) {
|
|
||||||
final var segments = enumCaseSwitchPattern.path().segments();
|
|
||||||
if (segments.size() != 2) {
|
|
||||||
return TypeView.unknown();
|
|
||||||
}
|
|
||||||
final var enumName = segments.getFirst();
|
|
||||||
final var caseName = segments.get(1);
|
|
||||||
final var enumCases = model.enums.get(enumName);
|
|
||||||
if (enumCases == null || !enumCases.contains(caseName)) {
|
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_SWITCH_PATTERN_TYPE_MISMATCH.name(),
|
|
||||||
"Enum case pattern '%s.%s' does not resolve".formatted(enumName, caseName),
|
|
||||||
enumCaseSwitchPattern.span());
|
|
||||||
return TypeView.unknown();
|
|
||||||
}
|
|
||||||
return TypeView.enumType(enumName);
|
|
||||||
}
|
|
||||||
if (pattern instanceof PbsAst.LiteralSwitchPattern literalSwitchPattern) {
|
|
||||||
final var literal = literalSwitchPattern.literal();
|
|
||||||
if (literal instanceof PbsAst.IntLiteralExpr || literal instanceof PbsAst.BoundedLiteralExpr) {
|
|
||||||
return TypeView.intType();
|
|
||||||
}
|
|
||||||
if (literal instanceof PbsAst.FloatLiteralExpr) {
|
|
||||||
return TypeView.floatType();
|
|
||||||
}
|
|
||||||
if (literal instanceof PbsAst.BoolLiteralExpr) {
|
|
||||||
return TypeView.bool();
|
|
||||||
}
|
|
||||||
if (literal instanceof PbsAst.StringLiteralExpr) {
|
|
||||||
return TypeView.str();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TypeView.unknown();
|
|
||||||
}
|
|
||||||
|
|
||||||
private TypeView analyzeHandleExpression(
|
|
||||||
final PbsAst.HandleExpr handleExpr,
|
|
||||||
final PbsFlowExpressionContext context) {
|
|
||||||
final var model = context.model();
|
|
||||||
final var diagnostics = context.diagnostics();
|
|
||||||
final var resultErrorName = context.resultErrorName();
|
|
||||||
final var sourceType = analyzeValueExpression(handleExpr.value(), context, null).type();
|
|
||||||
if (sourceType.kind() != Kind.RESULT) {
|
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_HANDLE_NON_RESULT.name(),
|
|
||||||
"Handle requires result expression",
|
|
||||||
handleExpr.value().span());
|
|
||||||
return TypeView.unknown();
|
|
||||||
}
|
|
||||||
if (resultErrorName == null) {
|
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_HANDLE_ERROR_MISMATCH.name(),
|
|
||||||
"Handle requires enclosing callable with result return",
|
|
||||||
handleExpr.span());
|
|
||||||
}
|
|
||||||
|
|
||||||
final var sourceErrorName = sourceType.errorType() == null ? null : sourceType.errorType().name();
|
|
||||||
final var sourceCases = sourceErrorName == null ? Set.<String>of() : model.errors.getOrDefault(sourceErrorName, Set.of());
|
|
||||||
final var sourcePayloadType = sourceType.inner() == null ? TypeView.unknown() : sourceType.inner();
|
|
||||||
final var matchedCases = new HashSet<String>();
|
|
||||||
var hasWildcard = false;
|
|
||||||
|
|
||||||
for (final var arm : handleExpr.arms()) {
|
|
||||||
if (arm.pattern() instanceof PbsAst.WildcardHandlePattern) {
|
|
||||||
hasWildcard = true;
|
|
||||||
} else if (arm.pattern() instanceof PbsAst.ErrorPathHandlePattern errorPathHandlePattern) {
|
|
||||||
final var segments = errorPathHandlePattern.path().segments();
|
|
||||||
if (segments.size() == 2) {
|
|
||||||
final var errorName = segments.getFirst();
|
|
||||||
final var caseName = segments.get(1);
|
|
||||||
if (!errorName.equals(sourceErrorName) || !sourceCases.contains(caseName)) {
|
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_HANDLE_ERROR_MISMATCH.name(),
|
|
||||||
"Handle arm pattern does not match source result error type",
|
|
||||||
arm.pattern().span());
|
|
||||||
} else if (!matchedCases.add(caseName)) {
|
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_HANDLE_ERROR_MISMATCH.name(),
|
|
||||||
"Handle arm duplicates same error case pattern",
|
|
||||||
arm.pattern().span());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arm.remapTarget() != null) {
|
|
||||||
if (!matchesTargetError(arm.remapTarget(), resultErrorName, model)) {
|
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_HANDLE_ERROR_MISMATCH.name(),
|
|
||||||
"Handle remap target must match enclosing callable result error type",
|
|
||||||
arm.remapTarget().span());
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
analyzeHandleBlockArm(
|
|
||||||
arm,
|
|
||||||
context,
|
|
||||||
sourcePayloadType,
|
|
||||||
model,
|
|
||||||
diagnostics);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasWildcard && !sourceCases.isEmpty() && !matchedCases.containsAll(sourceCases)) {
|
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_HANDLE_ERROR_MISMATCH.name(),
|
|
||||||
"Handle mapping is not exhaustive for source result error cases",
|
|
||||||
handleExpr.span());
|
|
||||||
}
|
|
||||||
|
|
||||||
return sourceType.inner() == null ? TypeView.unknown() : sourceType.inner();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void analyzeHandleBlockArm(
|
|
||||||
final PbsAst.HandleArm arm,
|
|
||||||
final PbsFlowExpressionContext context,
|
|
||||||
final TypeView sourcePayloadType,
|
|
||||||
final Model model,
|
|
||||||
final DiagnosticSink diagnostics) {
|
|
||||||
final var returnType = context.returnType();
|
|
||||||
final var resultErrorName = context.resultErrorName();
|
|
||||||
final var receiverType = context.receiverType();
|
|
||||||
final var scope = context.scope();
|
|
||||||
if (arm.block() == null) {
|
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_HANDLE_ARM_TERMINAL_INVALID.name(),
|
|
||||||
"Handle block arm must terminate with 'ok(...)' or 'err(E.case)'",
|
|
||||||
arm.span());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final var block = arm.block();
|
|
||||||
final var terminal = unwrapGroup(block.tailExpression());
|
|
||||||
if (terminal instanceof PbsAst.OkExpr okExpr) {
|
|
||||||
final var payloadBlock = new PbsAst.Block(block.statements(), okExpr.value(), block.span());
|
|
||||||
final var actualPayloadType = analyzeBlock(payloadBlock, context, true);
|
|
||||||
if (!typeOps.compatible(actualPayloadType, sourcePayloadType)) {
|
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_RESULT_OK_PAYLOAD_MISMATCH.name(),
|
|
||||||
"Handle arm 'ok(...)' payload is incompatible with source result payload type",
|
|
||||||
okExpr.value().span());
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (terminal instanceof PbsAst.ErrExpr errExpr) {
|
|
||||||
final var statementsOnly = new PbsAst.Block(block.statements(), null, block.span());
|
|
||||||
analyzeBlock(statementsOnly, context, false);
|
|
||||||
if (resultErrorName != null && !matchesTargetError(errExpr.errorPath(), resultErrorName, model)) {
|
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_RESULT_ERROR_LABEL_INVALID.name(),
|
|
||||||
"Error label in handle arm 'err(...)' does not match enclosing result error type",
|
|
||||||
errExpr.errorPath().span());
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block.tailExpression() == null) {
|
|
||||||
final var statementsOnly = new PbsAst.Block(block.statements(), null, block.span());
|
|
||||||
analyzeBlock(statementsOnly, context, false);
|
|
||||||
} else {
|
|
||||||
analyzeBlock(block, context, true);
|
|
||||||
}
|
|
||||||
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_HANDLE_ARM_TERMINAL_INVALID.name(),
|
|
||||||
"Handle block arm must terminate with 'ok(...)' or 'err(E.case)'",
|
|
||||||
block.span());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean matchesTargetError(
|
|
||||||
final PbsAst.ErrorPath path,
|
|
||||||
final String resultErrorName,
|
|
||||||
final Model model) {
|
|
||||||
if (path == null || resultErrorName == null || path.segments().size() != 2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final var errorName = path.segments().getFirst();
|
|
||||||
final var caseName = path.segments().get(1);
|
|
||||||
final var targetCases = model.errors.get(errorName);
|
|
||||||
return resultErrorName.equals(errorName) && targetCases != null && targetCases.contains(caseName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PbsAst.Expression unwrapGroup(final PbsAst.Expression expression) {
|
|
||||||
if (expression == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (expression instanceof PbsAst.GroupExpr groupExpr) {
|
|
||||||
return unwrapGroup(groupExpr.expression());
|
|
||||||
}
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExprResult analyzeValueExpression(
|
private ExprResult analyzeValueExpression(
|
||||||
final PbsAst.Expression expression,
|
final PbsAst.Expression expression,
|
||||||
final PbsFlowExpressionContext context,
|
final PbsFlowExpressionContext context,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user