implements PR-13.2
This commit is contained in:
parent
f8f520d284
commit
1137eaad56
@ -11,11 +11,14 @@ import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Model;
|
|||||||
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Scope;
|
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Scope;
|
||||||
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.TypeView;
|
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.TypeView;
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
final class PbsFlowBodyAnalyzer {
|
final class PbsFlowBodyAnalyzer {
|
||||||
private final PbsFlowTypeOps typeOps = new PbsFlowTypeOps();
|
private final PbsFlowTypeOps typeOps = new PbsFlowTypeOps();
|
||||||
private final PbsFlowExpressionAnalyzer expressionAnalyzer = new PbsFlowExpressionAnalyzer(typeOps);
|
private final PbsFlowExpressionAnalyzer expressionAnalyzer = new PbsFlowExpressionAnalyzer(typeOps);
|
||||||
|
private final PbsFlowCompletionAnalyzer completionAnalyzer = new PbsFlowCompletionAnalyzer();
|
||||||
|
private final PbsFlowCallableBodyAnalyzer callableBodyAnalyzer = new PbsFlowCallableBodyAnalyzer(
|
||||||
|
typeOps,
|
||||||
|
completionAnalyzer,
|
||||||
|
this::analyzeBlock);
|
||||||
|
|
||||||
private record AssignmentTargetResolution(
|
private record AssignmentTargetResolution(
|
||||||
TypeView type,
|
TypeView type,
|
||||||
@ -115,196 +118,7 @@ final class PbsFlowBodyAnalyzer {
|
|||||||
final ReadOnlyList<PbsAst.Parameter> parameters,
|
final ReadOnlyList<PbsAst.Parameter> parameters,
|
||||||
final PbsAst.Block body,
|
final PbsAst.Block body,
|
||||||
final PbsFlowBodyContext context) {
|
final PbsFlowBodyContext context) {
|
||||||
final var scope = context.scope();
|
callableBodyAnalyzer.validateCallableBody(parameters, body, context);
|
||||||
for (final var parameter : parameters) {
|
|
||||||
scope.bind(
|
|
||||||
parameter.name(),
|
|
||||||
typeOps.typeFromTypeRef(parameter.typeRef(), context.model(), context.receiverType()));
|
|
||||||
}
|
|
||||||
final var callableContext = context.withScope(scope);
|
|
||||||
analyzeBlock(body, callableContext, true);
|
|
||||||
validateCallableCompletion(body, callableContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateCallableCompletion(
|
|
||||||
final PbsAst.Block body,
|
|
||||||
final PbsFlowBodyContext context) {
|
|
||||||
final var returnType = context.returnType();
|
|
||||||
final var model = context.model();
|
|
||||||
final var diagnostics = context.diagnostics();
|
|
||||||
final var returnKind = returnType.kind();
|
|
||||||
final var enforceResultCompletion = returnKind == Kind.RESULT;
|
|
||||||
final var enforcePlainNonUnitCompletion = returnKind != Kind.RESULT
|
|
||||||
&& returnKind != Kind.UNIT
|
|
||||||
&& returnKind != Kind.OPTIONAL
|
|
||||||
&& returnKind != Kind.UNKNOWN;
|
|
||||||
|
|
||||||
if (!enforceResultCompletion && !enforcePlainNonUnitCompletion) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (blockAlwaysReturns(body, model)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enforceResultCompletion) {
|
|
||||||
Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_POSSIBLE_FALLTHROUGH_RESULT.name(),
|
|
||||||
"Possible fallthrough in result callable body; all paths must return explicitly",
|
|
||||||
body.span());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_POSSIBLE_FALLTHROUGH_NON_UNIT.name(),
|
|
||||||
"Possible fallthrough in plain non-unit callable body; all paths must return explicitly",
|
|
||||||
body.span());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean blockAlwaysReturns(
|
|
||||||
final PbsAst.Block block,
|
|
||||||
final Model model) {
|
|
||||||
if (block == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final var statement : block.statements()) {
|
|
||||||
if (statementAlwaysReturns(statement, model)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return expressionAlwaysReturns(block.tailExpression(), model);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean statementAlwaysReturns(
|
|
||||||
final PbsAst.Statement statement,
|
|
||||||
final Model model) {
|
|
||||||
if (statement instanceof PbsAst.ReturnStatement) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (statement instanceof PbsAst.IfStatement ifStatement) {
|
|
||||||
final var thenReturns = blockAlwaysReturns(ifStatement.thenBlock(), model);
|
|
||||||
final boolean elseReturns;
|
|
||||||
if (ifStatement.elseBlock() != null) {
|
|
||||||
elseReturns = blockAlwaysReturns(ifStatement.elseBlock(), model);
|
|
||||||
} else {
|
|
||||||
elseReturns = ifStatement.elseIf() != null && statementAlwaysReturns(ifStatement.elseIf(), model);
|
|
||||||
}
|
|
||||||
return thenReturns && elseReturns;
|
|
||||||
}
|
|
||||||
if (statement instanceof PbsAst.ExpressionStatement expressionStatement) {
|
|
||||||
return expressionAlwaysReturns(expressionStatement.expression(), model);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean expressionAlwaysReturns(
|
|
||||||
final PbsAst.Expression expression,
|
|
||||||
final Model model) {
|
|
||||||
return switch (expression) {
|
|
||||||
case PbsAst.GroupExpr groupExpr -> expressionAlwaysReturns(groupExpr.expression(), model);
|
|
||||||
case PbsAst.BlockExpr blockExpr -> blockAlwaysReturns(blockExpr.block(), model);
|
|
||||||
case PbsAst.IfExpr ifExpr -> blockAlwaysReturns(ifExpr.thenBlock(), model)
|
|
||||||
&& expressionAlwaysReturns(ifExpr.elseExpression(), model);
|
|
||||||
case PbsAst.SwitchExpr switchExpr -> switchAlwaysReturns(switchExpr, model);
|
|
||||||
case PbsAst.HandleExpr handleExpr -> handleAlwaysReturns(handleExpr, model);
|
|
||||||
case null, default -> false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean handleAlwaysReturns(
|
|
||||||
final PbsAst.HandleExpr handleExpr,
|
|
||||||
final Model model) {
|
|
||||||
if (expressionAlwaysReturns(handleExpr.value(), model)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (handleExpr.arms().isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (final var arm : handleExpr.arms()) {
|
|
||||||
if (!handleArmAlwaysReturns(arm, model)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean handleArmAlwaysReturns(
|
|
||||||
final PbsAst.HandleArm arm,
|
|
||||||
final Model model) {
|
|
||||||
if (arm.remapTarget() != null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (arm.block() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final var terminal = unwrapGroup(arm.block().tailExpression());
|
|
||||||
if (terminal instanceof PbsAst.ErrExpr errExpr) {
|
|
||||||
return isKnownErrorPath(errExpr.errorPath(), model);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isKnownErrorPath(
|
|
||||||
final PbsAst.ErrorPath path,
|
|
||||||
final Model model) {
|
|
||||||
if (path == null || path.segments().size() != 2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final var errorName = path.segments().getFirst();
|
|
||||||
final var caseName = path.segments().get(1);
|
|
||||||
final var errorCases = model.errors.get(errorName);
|
|
||||||
return errorCases != null && errorCases.contains(caseName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean switchAlwaysReturns(
|
|
||||||
final PbsAst.SwitchExpr switchExpr,
|
|
||||||
final Model model) {
|
|
||||||
if (switchExpr.arms().isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!switchIsExhaustive(switchExpr, model)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (final var arm : switchExpr.arms()) {
|
|
||||||
if (!blockAlwaysReturns(arm.block(), model)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean switchIsExhaustive(
|
|
||||||
final PbsAst.SwitchExpr switchExpr,
|
|
||||||
final Model model) {
|
|
||||||
for (final var arm : switchExpr.arms()) {
|
|
||||||
if (arm.pattern() instanceof PbsAst.WildcardSwitchPattern) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String enumName = null;
|
|
||||||
final var coveredCases = new HashSet<String>();
|
|
||||||
for (final var arm : switchExpr.arms()) {
|
|
||||||
if (!(arm.pattern() instanceof PbsAst.EnumCaseSwitchPattern enumCaseSwitchPattern)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final var segments = enumCaseSwitchPattern.path().segments();
|
|
||||||
if (segments.size() != 2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (enumName == null) {
|
|
||||||
enumName = segments.getFirst();
|
|
||||||
} else if (!enumName.equals(segments.getFirst())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
coveredCases.add(segments.get(1));
|
|
||||||
}
|
|
||||||
if (enumName == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final var enumCases = model.enums.get(enumName);
|
|
||||||
return enumCases != null && coveredCases.containsAll(enumCases);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeView analyzeBlock(
|
private TypeView analyzeBlock(
|
||||||
|
|||||||
@ -0,0 +1,40 @@
|
|||||||
|
package p.studio.compiler.pbs.semantics;
|
||||||
|
|
||||||
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.TypeView;
|
||||||
|
|
||||||
|
final class PbsFlowCallableBodyAnalyzer {
|
||||||
|
@FunctionalInterface
|
||||||
|
interface BlockAnalysisDelegate {
|
||||||
|
TypeView analyze(PbsAst.Block block, PbsFlowBodyContext context, boolean valueContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final PbsFlowTypeOps typeOps;
|
||||||
|
private final PbsFlowCompletionAnalyzer completionAnalyzer;
|
||||||
|
private final BlockAnalysisDelegate blockAnalysisDelegate;
|
||||||
|
|
||||||
|
PbsFlowCallableBodyAnalyzer(
|
||||||
|
final PbsFlowTypeOps typeOps,
|
||||||
|
final PbsFlowCompletionAnalyzer completionAnalyzer,
|
||||||
|
final BlockAnalysisDelegate blockAnalysisDelegate) {
|
||||||
|
this.typeOps = typeOps;
|
||||||
|
this.completionAnalyzer = completionAnalyzer;
|
||||||
|
this.blockAnalysisDelegate = blockAnalysisDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void validateCallableBody(
|
||||||
|
final ReadOnlyList<PbsAst.Parameter> parameters,
|
||||||
|
final PbsAst.Block body,
|
||||||
|
final PbsFlowBodyContext context) {
|
||||||
|
final var scope = context.scope();
|
||||||
|
for (final var parameter : parameters) {
|
||||||
|
scope.bind(
|
||||||
|
parameter.name(),
|
||||||
|
typeOps.typeFromTypeRef(parameter.typeRef(), context.model(), context.receiverType()));
|
||||||
|
}
|
||||||
|
final var callableContext = context.withScope(scope);
|
||||||
|
blockAnalysisDelegate.analyze(body, callableContext, true);
|
||||||
|
completionAnalyzer.validateCallableCompletion(body, callableContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,198 @@
|
|||||||
|
package p.studio.compiler.pbs.semantics;
|
||||||
|
|
||||||
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
|
import p.studio.compiler.source.diagnostics.Diagnostics;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Kind;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Model;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
final class PbsFlowCompletionAnalyzer {
|
||||||
|
void validateCallableCompletion(
|
||||||
|
final PbsAst.Block body,
|
||||||
|
final PbsFlowBodyContext context) {
|
||||||
|
final var returnType = context.returnType();
|
||||||
|
final var model = context.model();
|
||||||
|
final var diagnostics = context.diagnostics();
|
||||||
|
final var returnKind = returnType.kind();
|
||||||
|
final var enforceResultCompletion = returnKind == Kind.RESULT;
|
||||||
|
final var enforcePlainNonUnitCompletion = returnKind != Kind.RESULT
|
||||||
|
&& returnKind != Kind.UNIT
|
||||||
|
&& returnKind != Kind.OPTIONAL
|
||||||
|
&& returnKind != Kind.UNKNOWN;
|
||||||
|
|
||||||
|
if (!enforceResultCompletion && !enforcePlainNonUnitCompletion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (blockAlwaysReturns(body, model)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enforceResultCompletion) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_POSSIBLE_FALLTHROUGH_RESULT.name(),
|
||||||
|
"Possible fallthrough in result callable body; all paths must return explicitly",
|
||||||
|
body.span());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_POSSIBLE_FALLTHROUGH_NON_UNIT.name(),
|
||||||
|
"Possible fallthrough in plain non-unit callable body; all paths must return explicitly",
|
||||||
|
body.span());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean blockAlwaysReturns(
|
||||||
|
final PbsAst.Block block,
|
||||||
|
final Model model) {
|
||||||
|
if (block == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final var statement : block.statements()) {
|
||||||
|
if (statementAlwaysReturns(statement, model)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expressionAlwaysReturns(block.tailExpression(), model);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean statementAlwaysReturns(
|
||||||
|
final PbsAst.Statement statement,
|
||||||
|
final Model model) {
|
||||||
|
if (statement instanceof PbsAst.ReturnStatement) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (statement instanceof PbsAst.IfStatement ifStatement) {
|
||||||
|
final var thenReturns = blockAlwaysReturns(ifStatement.thenBlock(), model);
|
||||||
|
final boolean elseReturns;
|
||||||
|
if (ifStatement.elseBlock() != null) {
|
||||||
|
elseReturns = blockAlwaysReturns(ifStatement.elseBlock(), model);
|
||||||
|
} else {
|
||||||
|
elseReturns = ifStatement.elseIf() != null && statementAlwaysReturns(ifStatement.elseIf(), model);
|
||||||
|
}
|
||||||
|
return thenReturns && elseReturns;
|
||||||
|
}
|
||||||
|
if (statement instanceof PbsAst.ExpressionStatement expressionStatement) {
|
||||||
|
return expressionAlwaysReturns(expressionStatement.expression(), model);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean expressionAlwaysReturns(
|
||||||
|
final PbsAst.Expression expression,
|
||||||
|
final Model model) {
|
||||||
|
return switch (expression) {
|
||||||
|
case PbsAst.GroupExpr groupExpr -> expressionAlwaysReturns(groupExpr.expression(), model);
|
||||||
|
case PbsAst.BlockExpr blockExpr -> blockAlwaysReturns(blockExpr.block(), model);
|
||||||
|
case PbsAst.IfExpr ifExpr -> blockAlwaysReturns(ifExpr.thenBlock(), model)
|
||||||
|
&& expressionAlwaysReturns(ifExpr.elseExpression(), model);
|
||||||
|
case PbsAst.SwitchExpr switchExpr -> switchAlwaysReturns(switchExpr, model);
|
||||||
|
case PbsAst.HandleExpr handleExpr -> handleAlwaysReturns(handleExpr, model);
|
||||||
|
case null, default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleAlwaysReturns(
|
||||||
|
final PbsAst.HandleExpr handleExpr,
|
||||||
|
final Model model) {
|
||||||
|
if (expressionAlwaysReturns(handleExpr.value(), model)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (handleExpr.arms().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (final var arm : handleExpr.arms()) {
|
||||||
|
if (!handleArmAlwaysReturns(arm, model)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleArmAlwaysReturns(
|
||||||
|
final PbsAst.HandleArm arm,
|
||||||
|
final Model model) {
|
||||||
|
if (arm.remapTarget() != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (arm.block() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final var terminal = unwrapGroup(arm.block().tailExpression());
|
||||||
|
if (terminal instanceof PbsAst.ErrExpr errExpr) {
|
||||||
|
return isKnownErrorPath(errExpr.errorPath(), model);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isKnownErrorPath(
|
||||||
|
final PbsAst.ErrorPath path,
|
||||||
|
final Model model) {
|
||||||
|
if (path == null || path.segments().size() != 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final var errorName = path.segments().getFirst();
|
||||||
|
final var caseName = path.segments().get(1);
|
||||||
|
final var errorCases = model.errors.get(errorName);
|
||||||
|
return errorCases != null && errorCases.contains(caseName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean switchAlwaysReturns(
|
||||||
|
final PbsAst.SwitchExpr switchExpr,
|
||||||
|
final Model model) {
|
||||||
|
if (switchExpr.arms().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!switchIsExhaustive(switchExpr, model)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (final var arm : switchExpr.arms()) {
|
||||||
|
if (!blockAlwaysReturns(arm.block(), model)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean switchIsExhaustive(
|
||||||
|
final PbsAst.SwitchExpr switchExpr,
|
||||||
|
final Model model) {
|
||||||
|
for (final var arm : switchExpr.arms()) {
|
||||||
|
if (arm.pattern() instanceof PbsAst.WildcardSwitchPattern) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String enumName = null;
|
||||||
|
final var coveredCases = new HashSet<String>();
|
||||||
|
for (final var arm : switchExpr.arms()) {
|
||||||
|
if (!(arm.pattern() instanceof PbsAst.EnumCaseSwitchPattern enumCaseSwitchPattern)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final var segments = enumCaseSwitchPattern.path().segments();
|
||||||
|
if (segments.size() != 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (enumName == null) {
|
||||||
|
enumName = segments.getFirst();
|
||||||
|
} else if (!enumName.equals(segments.getFirst())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
coveredCases.add(segments.get(1));
|
||||||
|
}
|
||||||
|
if (enumName == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var enumCases = model.enums.get(enumName);
|
||||||
|
return enumCases != null && coveredCases.containsAll(enumCases);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsAst.Expression unwrapGroup(final PbsAst.Expression expression) {
|
||||||
|
if (expression instanceof PbsAst.GroupExpr groupExpr) {
|
||||||
|
return unwrapGroup(groupExpr.expression());
|
||||||
|
}
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user