From dd598b3989eb5ddd99752400990f222e8e7999c9 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 10 Mar 2026 10:18:37 +0000 Subject: [PATCH] implements PR-12.4 --- .../PbsFlowControlExpressionAnalyzer.java | 351 ++++++++++++++++++ .../semantics/PbsFlowExpressionAnalyzer.java | 321 +--------------- 2 files changed, 358 insertions(+), 314 deletions(-) create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowControlExpressionAnalyzer.java diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowControlExpressionAnalyzer.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowControlExpressionAnalyzer.java new file mode 100644 index 00000000..a7f06532 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowControlExpressionAnalyzer.java @@ -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(); + 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(); + 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.of() : model.errors.getOrDefault(sourceErrorName, Set.of()); + final var sourcePayloadType = sourceType.inner() == null ? TypeView.unknown() : sourceType.inner(); + final var matchedCases = new HashSet(); + 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 ""; + } + 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 ""; + } + + 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)); + } +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowExpressionAnalyzer.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowExpressionAnalyzer.java index 601844c9..6508ae6d 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowExpressionAnalyzer.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowExpressionAnalyzer.java @@ -34,6 +34,7 @@ final class PbsFlowExpressionAnalyzer { private final PbsFlowTypeOps typeOps; private final PbsFlowStructuralExpressionAnalyzer structuralExpressionAnalyzer; private final PbsFlowCallableResolutionAnalyzer callableResolutionAnalyzer; + private final PbsFlowControlExpressionAnalyzer controlExpressionAnalyzer; PbsFlowExpressionAnalyzer(final PbsFlowTypeOps typeOps) { this.typeOps = typeOps; @@ -44,6 +45,10 @@ final class PbsFlowExpressionAnalyzer { this.callableResolutionAnalyzer = new PbsFlowCallableResolutionAnalyzer( typeOps, this::analyzeExpressionInternal); + this.controlExpressionAnalyzer = new PbsFlowControlExpressionAnalyzer( + typeOps, + this::analyzeExpressionInternal, + this::analyzeBlock); } ExprResult analyzeExpression( @@ -119,7 +124,7 @@ final class PbsFlowExpressionAnalyzer { return ExprResult.type(thenType); } 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) { final var optional = analyzeValueExpression(elseExpr.optionalExpression(), context, null).type(); @@ -161,7 +166,7 @@ final class PbsFlowExpressionAnalyzer { return ExprResult.type(source.inner()); } 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) { return callableResolutionAnalyzer.analyzeBindExpression(bindExpr, context); @@ -170,318 +175,6 @@ final class PbsFlowExpressionAnalyzer { 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(); - 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(); - 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 ""; - } - 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 ""; - } - - 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.of() : model.errors.getOrDefault(sourceErrorName, Set.of()); - final var sourcePayloadType = sourceType.inner() == null ? TypeView.unknown() : sourceType.inner(); - final var matchedCases = new HashSet(); - 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( final PbsAst.Expression expression, final PbsFlowExpressionContext context,