implements PR-13.3
This commit is contained in:
parent
1137eaad56
commit
114451e23a
@ -1,7 +1,6 @@
|
|||||||
package p.studio.compiler.pbs.semantics;
|
package p.studio.compiler.pbs.semantics;
|
||||||
|
|
||||||
import p.studio.compiler.pbs.ast.PbsAst;
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
import p.studio.compiler.source.Span;
|
|
||||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
import p.studio.compiler.source.diagnostics.Diagnostics;
|
import p.studio.compiler.source.diagnostics.Diagnostics;
|
||||||
import p.studio.utilities.structures.ReadOnlyList;
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
@ -15,6 +14,10 @@ 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 PbsFlowCompletionAnalyzer completionAnalyzer = new PbsFlowCompletionAnalyzer();
|
||||||
|
private final PbsFlowStatementAnalyzer statementAnalyzer = new PbsFlowStatementAnalyzer(
|
||||||
|
typeOps,
|
||||||
|
expressionAnalyzer,
|
||||||
|
this::analyzeAssignmentStatement);
|
||||||
private final PbsFlowCallableBodyAnalyzer callableBodyAnalyzer = new PbsFlowCallableBodyAnalyzer(
|
private final PbsFlowCallableBodyAnalyzer callableBodyAnalyzer = new PbsFlowCallableBodyAnalyzer(
|
||||||
typeOps,
|
typeOps,
|
||||||
completionAnalyzer,
|
completionAnalyzer,
|
||||||
@ -146,307 +149,7 @@ final class PbsFlowBodyAnalyzer {
|
|||||||
final PbsAst.Block block,
|
final PbsAst.Block block,
|
||||||
final PbsFlowBodyContext context,
|
final PbsFlowBodyContext context,
|
||||||
final boolean valueContext) {
|
final boolean valueContext) {
|
||||||
final var scope = context.scope().copy();
|
return statementAnalyzer.analyzeBlock(block, context, valueContext);
|
||||||
final var scopedContext = context.withScope(scope);
|
|
||||||
for (final var statement : block.statements()) {
|
|
||||||
analyzeStatement(statement, scopedContext);
|
|
||||||
}
|
|
||||||
if (block.tailExpression() == null) {
|
|
||||||
return TypeView.unit();
|
|
||||||
}
|
|
||||||
return expressionAnalyzer.analyzeExpression(
|
|
||||||
block.tailExpression(),
|
|
||||||
scope,
|
|
||||||
null,
|
|
||||||
context.returnType(),
|
|
||||||
context.resultErrorName(),
|
|
||||||
context.receiverType(),
|
|
||||||
context.model(),
|
|
||||||
context.diagnostics(),
|
|
||||||
ExprUse.VALUE,
|
|
||||||
valueContext,
|
|
||||||
this::analyzeBlock).type();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void analyzeStatement(
|
|
||||||
final PbsAst.Statement statement,
|
|
||||||
final PbsFlowBodyContext context) {
|
|
||||||
final var scope = context.scope();
|
|
||||||
final var returnType = context.returnType();
|
|
||||||
final var resultErrorName = context.resultErrorName();
|
|
||||||
final var receiverType = context.receiverType();
|
|
||||||
final var model = context.model();
|
|
||||||
final var diagnostics = context.diagnostics();
|
|
||||||
if (statement instanceof PbsAst.LetStatement letStatement) {
|
|
||||||
final var expected = letStatement.explicitType() == null
|
|
||||||
? null
|
|
||||||
: typeOps.typeFromTypeRef(letStatement.explicitType(), model, receiverType);
|
|
||||||
final var initializer = expressionAnalyzer.analyzeExpression(
|
|
||||||
letStatement.initializer(),
|
|
||||||
scope,
|
|
||||||
expected,
|
|
||||||
returnType,
|
|
||||||
resultErrorName,
|
|
||||||
receiverType,
|
|
||||||
model,
|
|
||||||
diagnostics,
|
|
||||||
ExprUse.VALUE,
|
|
||||||
true,
|
|
||||||
this::analyzeBlock);
|
|
||||||
scope.bind(letStatement.name(), expected == null ? initializer.type() : expected, !letStatement.isConst());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (statement instanceof PbsAst.ReturnStatement returnStatement) {
|
|
||||||
if (returnStatement.value() != null) {
|
|
||||||
analyzeReturnStatement(returnStatement.value(), context);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (statement instanceof PbsAst.IfStatement ifStatement) {
|
|
||||||
final var condition = expressionAnalyzer.analyzeExpression(
|
|
||||||
ifStatement.condition(),
|
|
||||||
scope,
|
|
||||||
TypeView.bool(),
|
|
||||||
returnType,
|
|
||||||
resultErrorName,
|
|
||||||
receiverType,
|
|
||||||
model,
|
|
||||||
diagnostics,
|
|
||||||
ExprUse.VALUE,
|
|
||||||
true,
|
|
||||||
this::analyzeBlock).type();
|
|
||||||
if (!typeOps.isBool(condition)) {
|
|
||||||
Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_IF_NON_BOOL_CONDITION.name(),
|
|
||||||
"If statement condition must have bool type",
|
|
||||||
ifStatement.condition().span());
|
|
||||||
}
|
|
||||||
analyzeBlock(ifStatement.thenBlock(), context, false);
|
|
||||||
if (ifStatement.elseBlock() != null) {
|
|
||||||
analyzeBlock(ifStatement.elseBlock(), context, false);
|
|
||||||
}
|
|
||||||
if (ifStatement.elseIf() != null) {
|
|
||||||
analyzeStatement(ifStatement.elseIf(), context);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (statement instanceof PbsAst.ForStatement(
|
|
||||||
String iteratorName, PbsAst.TypeRef type, PbsAst.Expression fromExpression,
|
|
||||||
PbsAst.Expression untilExpression, PbsAst.Expression stepExpression, PbsAst.Block body,
|
|
||||||
Span span
|
|
||||||
)) {
|
|
||||||
final var iteratorType = typeOps.typeFromTypeRef(type, model, receiverType);
|
|
||||||
final var fromType = expressionAnalyzer.analyzeExpression(
|
|
||||||
fromExpression,
|
|
||||||
scope,
|
|
||||||
iteratorType,
|
|
||||||
returnType,
|
|
||||||
resultErrorName,
|
|
||||||
receiverType,
|
|
||||||
model,
|
|
||||||
diagnostics,
|
|
||||||
ExprUse.VALUE,
|
|
||||||
true,
|
|
||||||
this::analyzeBlock).type();
|
|
||||||
final var untilType = expressionAnalyzer.analyzeExpression(
|
|
||||||
untilExpression,
|
|
||||||
scope,
|
|
||||||
iteratorType,
|
|
||||||
returnType,
|
|
||||||
resultErrorName,
|
|
||||||
receiverType,
|
|
||||||
model,
|
|
||||||
diagnostics,
|
|
||||||
ExprUse.VALUE,
|
|
||||||
true,
|
|
||||||
this::analyzeBlock).type();
|
|
||||||
if (!typeOps.compatible(fromType, iteratorType) || !typeOps.compatible(untilType, iteratorType)) {
|
|
||||||
Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_FOR_TYPE_MISMATCH.name(),
|
|
||||||
"For-loop bounds must match declared iterator type",
|
|
||||||
span);
|
|
||||||
}
|
|
||||||
if (stepExpression != null) {
|
|
||||||
final var stepType = expressionAnalyzer.analyzeExpression(
|
|
||||||
stepExpression,
|
|
||||||
scope,
|
|
||||||
iteratorType,
|
|
||||||
returnType,
|
|
||||||
resultErrorName,
|
|
||||||
receiverType,
|
|
||||||
model,
|
|
||||||
diagnostics,
|
|
||||||
ExprUse.VALUE,
|
|
||||||
true,
|
|
||||||
this::analyzeBlock).type();
|
|
||||||
if (!typeOps.compatible(stepType, iteratorType)) {
|
|
||||||
Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_FOR_TYPE_MISMATCH.name(),
|
|
||||||
"For-loop step must match declared iterator type",
|
|
||||||
stepExpression.span());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final var bodyScope = scope.copy();
|
|
||||||
bodyScope.bind(iteratorName, iteratorType);
|
|
||||||
analyzeBlock(body, context.withScope(bodyScope), false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (statement instanceof PbsAst.WhileStatement whileStatement) {
|
|
||||||
final var condition = expressionAnalyzer.analyzeExpression(
|
|
||||||
whileStatement.condition(),
|
|
||||||
scope,
|
|
||||||
TypeView.bool(),
|
|
||||||
returnType,
|
|
||||||
resultErrorName,
|
|
||||||
receiverType,
|
|
||||||
model,
|
|
||||||
diagnostics,
|
|
||||||
ExprUse.VALUE,
|
|
||||||
true,
|
|
||||||
this::analyzeBlock).type();
|
|
||||||
if (!typeOps.isBool(condition)) {
|
|
||||||
Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_WHILE_NON_BOOL_CONDITION.name(),
|
|
||||||
"While condition must have bool type",
|
|
||||||
whileStatement.condition().span());
|
|
||||||
}
|
|
||||||
analyzeBlock(whileStatement.body(), context, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (statement instanceof PbsAst.ExpressionStatement expressionStatement) {
|
|
||||||
expressionAnalyzer.analyzeExpression(
|
|
||||||
expressionStatement.expression(),
|
|
||||||
scope,
|
|
||||||
null,
|
|
||||||
returnType,
|
|
||||||
resultErrorName,
|
|
||||||
receiverType,
|
|
||||||
model,
|
|
||||||
diagnostics,
|
|
||||||
ExprUse.VALUE,
|
|
||||||
false,
|
|
||||||
this::analyzeBlock);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (statement instanceof PbsAst.AssignStatement assignStatement) {
|
|
||||||
analyzeAssignmentStatement(assignStatement, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void analyzeReturnStatement(
|
|
||||||
final PbsAst.Expression value,
|
|
||||||
final PbsFlowBodyContext context) {
|
|
||||||
final var root = unwrapGroup(value);
|
|
||||||
if (root instanceof PbsAst.OkExpr okExpr) {
|
|
||||||
analyzeReturnOk(okExpr, context);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (root instanceof PbsAst.ErrExpr errExpr) {
|
|
||||||
analyzeReturnErr(errExpr, context);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
expressionAnalyzer.analyzeExpression(
|
|
||||||
value,
|
|
||||||
context.scope(),
|
|
||||||
context.returnType(),
|
|
||||||
context.returnType(),
|
|
||||||
context.resultErrorName(),
|
|
||||||
context.receiverType(),
|
|
||||||
context.model(),
|
|
||||||
context.diagnostics(),
|
|
||||||
ExprUse.VALUE,
|
|
||||||
true,
|
|
||||||
this::analyzeBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void analyzeReturnOk(
|
|
||||||
final PbsAst.OkExpr okExpr,
|
|
||||||
final PbsFlowBodyContext context) {
|
|
||||||
final var returnType = context.returnType();
|
|
||||||
final var resultErrorName = context.resultErrorName();
|
|
||||||
final var diagnostics = context.diagnostics();
|
|
||||||
if (returnType.kind() != Kind.RESULT || resultErrorName == null) {
|
|
||||||
Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_RESULT_FLOW_INVALID_POSITION.name(),
|
|
||||||
"'ok(...)' is only allowed when returning from a result callable",
|
|
||||||
okExpr.span());
|
|
||||||
expressionAnalyzer.analyzeExpression(
|
|
||||||
okExpr.value(),
|
|
||||||
context.scope(),
|
|
||||||
null,
|
|
||||||
returnType,
|
|
||||||
resultErrorName,
|
|
||||||
context.receiverType(),
|
|
||||||
context.model(),
|
|
||||||
diagnostics,
|
|
||||||
ExprUse.VALUE,
|
|
||||||
true,
|
|
||||||
this::analyzeBlock);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final var payloadType = returnType.inner();
|
|
||||||
final var actualType = expressionAnalyzer.analyzeExpression(
|
|
||||||
okExpr.value(),
|
|
||||||
context.scope(),
|
|
||||||
payloadType,
|
|
||||||
returnType,
|
|
||||||
resultErrorName,
|
|
||||||
context.receiverType(),
|
|
||||||
context.model(),
|
|
||||||
diagnostics,
|
|
||||||
ExprUse.VALUE,
|
|
||||||
true,
|
|
||||||
this::analyzeBlock).type();
|
|
||||||
if (!typeOps.compatible(actualType, payloadType)) {
|
|
||||||
Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_RESULT_OK_PAYLOAD_MISMATCH.name(),
|
|
||||||
"Payload in 'ok(...)' is incompatible with result payload type",
|
|
||||||
okExpr.value().span());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void analyzeReturnErr(
|
|
||||||
final PbsAst.ErrExpr errExpr,
|
|
||||||
final PbsFlowBodyContext context) {
|
|
||||||
final var returnType = context.returnType();
|
|
||||||
final var resultErrorName = context.resultErrorName();
|
|
||||||
final var model = context.model();
|
|
||||||
final var diagnostics = context.diagnostics();
|
|
||||||
if (returnType.kind() != Kind.RESULT || resultErrorName == null) {
|
|
||||||
Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_RESULT_FLOW_INVALID_POSITION.name(),
|
|
||||||
"'err(...)' is only allowed when returning from a result callable",
|
|
||||||
errExpr.span());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!matchesTargetError(errExpr.errorPath(), resultErrorName, model)) {
|
|
||||||
Diagnostics.error(diagnostics,
|
|
||||||
PbsSemanticsErrors.E_SEM_RESULT_ERROR_LABEL_INVALID.name(),
|
|
||||||
"Error label in 'err(...)' does not match enclosing result error type",
|
|
||||||
errExpr.errorPath().span());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PbsAst.Expression unwrapGroup(final PbsAst.Expression expression) {
|
|
||||||
if (expression instanceof PbsAst.GroupExpr groupExpr) {
|
|
||||||
return unwrapGroup(groupExpr.expression());
|
|
||||||
}
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 void analyzeAssignmentStatement(
|
private void analyzeAssignmentStatement(
|
||||||
|
|||||||
@ -0,0 +1,384 @@
|
|||||||
|
package p.studio.compiler.pbs.semantics;
|
||||||
|
|
||||||
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
|
import p.studio.compiler.source.Span;
|
||||||
|
import p.studio.compiler.source.diagnostics.Diagnostics;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.ExprUse;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Kind;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Model;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Scope;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.TypeView;
|
||||||
|
|
||||||
|
final class PbsFlowStatementAnalyzer {
|
||||||
|
@FunctionalInterface
|
||||||
|
interface AssignmentStatementAnalyzer {
|
||||||
|
void analyze(PbsAst.AssignStatement assignStatement, PbsFlowBodyContext context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final PbsFlowTypeOps typeOps;
|
||||||
|
private final PbsFlowExpressionAnalyzer expressionAnalyzer;
|
||||||
|
private final AssignmentStatementAnalyzer assignmentStatementAnalyzer;
|
||||||
|
|
||||||
|
PbsFlowStatementAnalyzer(
|
||||||
|
final PbsFlowTypeOps typeOps,
|
||||||
|
final PbsFlowExpressionAnalyzer expressionAnalyzer,
|
||||||
|
final AssignmentStatementAnalyzer assignmentStatementAnalyzer) {
|
||||||
|
this.typeOps = typeOps;
|
||||||
|
this.expressionAnalyzer = expressionAnalyzer;
|
||||||
|
this.assignmentStatementAnalyzer = assignmentStatementAnalyzer;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeView analyzeBlock(
|
||||||
|
final PbsAst.Block block,
|
||||||
|
final PbsFlowBodyContext context,
|
||||||
|
final boolean valueContext) {
|
||||||
|
final var scope = context.scope().copy();
|
||||||
|
final var scopedContext = context.withScope(scope);
|
||||||
|
for (final var statement : block.statements()) {
|
||||||
|
analyzeStatement(statement, scopedContext);
|
||||||
|
}
|
||||||
|
if (block.tailExpression() == null) {
|
||||||
|
return TypeView.unit();
|
||||||
|
}
|
||||||
|
return expressionAnalyzer.analyzeExpression(
|
||||||
|
block.tailExpression(),
|
||||||
|
scope,
|
||||||
|
null,
|
||||||
|
context.returnType(),
|
||||||
|
context.resultErrorName(),
|
||||||
|
context.receiverType(),
|
||||||
|
context.model(),
|
||||||
|
context.diagnostics(),
|
||||||
|
ExprUse.VALUE,
|
||||||
|
valueContext,
|
||||||
|
this::analyzeNestedBlock).type();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeView analyzeNestedBlock(
|
||||||
|
final PbsAst.Block block,
|
||||||
|
final Scope outerScope,
|
||||||
|
final TypeView returnType,
|
||||||
|
final String resultErrorName,
|
||||||
|
final TypeView receiverType,
|
||||||
|
final Model model,
|
||||||
|
final p.studio.compiler.source.diagnostics.DiagnosticSink diagnostics,
|
||||||
|
final boolean valueContext) {
|
||||||
|
return analyzeBlock(
|
||||||
|
block,
|
||||||
|
new PbsFlowBodyContext(
|
||||||
|
outerScope,
|
||||||
|
returnType,
|
||||||
|
resultErrorName,
|
||||||
|
receiverType,
|
||||||
|
model,
|
||||||
|
diagnostics),
|
||||||
|
valueContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void analyzeStatement(
|
||||||
|
final PbsAst.Statement statement,
|
||||||
|
final PbsFlowBodyContext context) {
|
||||||
|
final var scope = context.scope();
|
||||||
|
final var returnType = context.returnType();
|
||||||
|
final var resultErrorName = context.resultErrorName();
|
||||||
|
final var receiverType = context.receiverType();
|
||||||
|
final var model = context.model();
|
||||||
|
final var diagnostics = context.diagnostics();
|
||||||
|
if (statement instanceof PbsAst.LetStatement letStatement) {
|
||||||
|
final var expected = letStatement.explicitType() == null
|
||||||
|
? null
|
||||||
|
: typeOps.typeFromTypeRef(letStatement.explicitType(), model, receiverType);
|
||||||
|
final var initializer = expressionAnalyzer.analyzeExpression(
|
||||||
|
letStatement.initializer(),
|
||||||
|
scope,
|
||||||
|
expected,
|
||||||
|
returnType,
|
||||||
|
resultErrorName,
|
||||||
|
receiverType,
|
||||||
|
model,
|
||||||
|
diagnostics,
|
||||||
|
ExprUse.VALUE,
|
||||||
|
true,
|
||||||
|
this::analyzeNestedBlock);
|
||||||
|
scope.bind(letStatement.name(), expected == null ? initializer.type() : expected, !letStatement.isConst());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (statement instanceof PbsAst.ReturnStatement returnStatement) {
|
||||||
|
if (returnStatement.value() != null) {
|
||||||
|
analyzeReturnStatement(returnStatement.value(), context);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (statement instanceof PbsAst.IfStatement ifStatement) {
|
||||||
|
final var condition = expressionAnalyzer.analyzeExpression(
|
||||||
|
ifStatement.condition(),
|
||||||
|
scope,
|
||||||
|
TypeView.bool(),
|
||||||
|
returnType,
|
||||||
|
resultErrorName,
|
||||||
|
receiverType,
|
||||||
|
model,
|
||||||
|
diagnostics,
|
||||||
|
ExprUse.VALUE,
|
||||||
|
true,
|
||||||
|
this::analyzeNestedBlock).type();
|
||||||
|
if (!typeOps.isBool(condition)) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_IF_NON_BOOL_CONDITION.name(),
|
||||||
|
"If statement condition must have bool type",
|
||||||
|
ifStatement.condition().span());
|
||||||
|
}
|
||||||
|
analyzeBlock(ifStatement.thenBlock(), context, false);
|
||||||
|
if (ifStatement.elseBlock() != null) {
|
||||||
|
analyzeBlock(ifStatement.elseBlock(), context, false);
|
||||||
|
}
|
||||||
|
if (ifStatement.elseIf() != null) {
|
||||||
|
analyzeStatement(ifStatement.elseIf(), context);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (statement instanceof PbsAst.ForStatement(
|
||||||
|
String iteratorName, PbsAst.TypeRef type, PbsAst.Expression fromExpression,
|
||||||
|
PbsAst.Expression untilExpression, PbsAst.Expression stepExpression, PbsAst.Block body,
|
||||||
|
Span span
|
||||||
|
)) {
|
||||||
|
analyzeForStatement(
|
||||||
|
iteratorName,
|
||||||
|
type,
|
||||||
|
fromExpression,
|
||||||
|
untilExpression,
|
||||||
|
stepExpression,
|
||||||
|
body,
|
||||||
|
span,
|
||||||
|
context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (statement instanceof PbsAst.WhileStatement whileStatement) {
|
||||||
|
final var condition = expressionAnalyzer.analyzeExpression(
|
||||||
|
whileStatement.condition(),
|
||||||
|
scope,
|
||||||
|
TypeView.bool(),
|
||||||
|
returnType,
|
||||||
|
resultErrorName,
|
||||||
|
receiverType,
|
||||||
|
model,
|
||||||
|
diagnostics,
|
||||||
|
ExprUse.VALUE,
|
||||||
|
true,
|
||||||
|
this::analyzeNestedBlock).type();
|
||||||
|
if (!typeOps.isBool(condition)) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_WHILE_NON_BOOL_CONDITION.name(),
|
||||||
|
"While condition must have bool type",
|
||||||
|
whileStatement.condition().span());
|
||||||
|
}
|
||||||
|
analyzeBlock(whileStatement.body(), context, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (statement instanceof PbsAst.ExpressionStatement expressionStatement) {
|
||||||
|
expressionAnalyzer.analyzeExpression(
|
||||||
|
expressionStatement.expression(),
|
||||||
|
scope,
|
||||||
|
null,
|
||||||
|
returnType,
|
||||||
|
resultErrorName,
|
||||||
|
receiverType,
|
||||||
|
model,
|
||||||
|
diagnostics,
|
||||||
|
ExprUse.VALUE,
|
||||||
|
false,
|
||||||
|
this::analyzeNestedBlock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (statement instanceof PbsAst.AssignStatement assignStatement) {
|
||||||
|
assignmentStatementAnalyzer.analyze(assignStatement, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void analyzeForStatement(
|
||||||
|
final String iteratorName,
|
||||||
|
final PbsAst.TypeRef type,
|
||||||
|
final PbsAst.Expression fromExpression,
|
||||||
|
final PbsAst.Expression untilExpression,
|
||||||
|
final PbsAst.Expression stepExpression,
|
||||||
|
final PbsAst.Block body,
|
||||||
|
final Span span,
|
||||||
|
final PbsFlowBodyContext context) {
|
||||||
|
final var scope = context.scope();
|
||||||
|
final var returnType = context.returnType();
|
||||||
|
final var resultErrorName = context.resultErrorName();
|
||||||
|
final var receiverType = context.receiverType();
|
||||||
|
final var model = context.model();
|
||||||
|
final var diagnostics = context.diagnostics();
|
||||||
|
final var iteratorType = typeOps.typeFromTypeRef(type, model, receiverType);
|
||||||
|
final var fromType = expressionAnalyzer.analyzeExpression(
|
||||||
|
fromExpression,
|
||||||
|
scope,
|
||||||
|
iteratorType,
|
||||||
|
returnType,
|
||||||
|
resultErrorName,
|
||||||
|
receiverType,
|
||||||
|
model,
|
||||||
|
diagnostics,
|
||||||
|
ExprUse.VALUE,
|
||||||
|
true,
|
||||||
|
this::analyzeNestedBlock).type();
|
||||||
|
final var untilType = expressionAnalyzer.analyzeExpression(
|
||||||
|
untilExpression,
|
||||||
|
scope,
|
||||||
|
iteratorType,
|
||||||
|
returnType,
|
||||||
|
resultErrorName,
|
||||||
|
receiverType,
|
||||||
|
model,
|
||||||
|
diagnostics,
|
||||||
|
ExprUse.VALUE,
|
||||||
|
true,
|
||||||
|
this::analyzeNestedBlock).type();
|
||||||
|
if (!typeOps.compatible(fromType, iteratorType) || !typeOps.compatible(untilType, iteratorType)) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_FOR_TYPE_MISMATCH.name(),
|
||||||
|
"For-loop bounds must match declared iterator type",
|
||||||
|
span);
|
||||||
|
}
|
||||||
|
if (stepExpression != null) {
|
||||||
|
final var stepType = expressionAnalyzer.analyzeExpression(
|
||||||
|
stepExpression,
|
||||||
|
scope,
|
||||||
|
iteratorType,
|
||||||
|
returnType,
|
||||||
|
resultErrorName,
|
||||||
|
receiverType,
|
||||||
|
model,
|
||||||
|
diagnostics,
|
||||||
|
ExprUse.VALUE,
|
||||||
|
true,
|
||||||
|
this::analyzeNestedBlock).type();
|
||||||
|
if (!typeOps.compatible(stepType, iteratorType)) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_FOR_TYPE_MISMATCH.name(),
|
||||||
|
"For-loop step must match declared iterator type",
|
||||||
|
stepExpression.span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final var bodyScope = scope.copy();
|
||||||
|
bodyScope.bind(iteratorName, iteratorType);
|
||||||
|
analyzeBlock(body, context.withScope(bodyScope), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void analyzeReturnStatement(
|
||||||
|
final PbsAst.Expression value,
|
||||||
|
final PbsFlowBodyContext context) {
|
||||||
|
final var root = unwrapGroup(value);
|
||||||
|
if (root instanceof PbsAst.OkExpr okExpr) {
|
||||||
|
analyzeReturnOk(okExpr, context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (root instanceof PbsAst.ErrExpr errExpr) {
|
||||||
|
analyzeReturnErr(errExpr, context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expressionAnalyzer.analyzeExpression(
|
||||||
|
value,
|
||||||
|
context.scope(),
|
||||||
|
context.returnType(),
|
||||||
|
context.returnType(),
|
||||||
|
context.resultErrorName(),
|
||||||
|
context.receiverType(),
|
||||||
|
context.model(),
|
||||||
|
context.diagnostics(),
|
||||||
|
ExprUse.VALUE,
|
||||||
|
true,
|
||||||
|
this::analyzeNestedBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void analyzeReturnOk(
|
||||||
|
final PbsAst.OkExpr okExpr,
|
||||||
|
final PbsFlowBodyContext context) {
|
||||||
|
final var returnType = context.returnType();
|
||||||
|
final var resultErrorName = context.resultErrorName();
|
||||||
|
final var diagnostics = context.diagnostics();
|
||||||
|
if (returnType.kind() != Kind.RESULT || resultErrorName == null) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_RESULT_FLOW_INVALID_POSITION.name(),
|
||||||
|
"'ok(...)' is only allowed when returning from a result callable",
|
||||||
|
okExpr.span());
|
||||||
|
expressionAnalyzer.analyzeExpression(
|
||||||
|
okExpr.value(),
|
||||||
|
context.scope(),
|
||||||
|
null,
|
||||||
|
returnType,
|
||||||
|
resultErrorName,
|
||||||
|
context.receiverType(),
|
||||||
|
context.model(),
|
||||||
|
diagnostics,
|
||||||
|
ExprUse.VALUE,
|
||||||
|
true,
|
||||||
|
this::analyzeNestedBlock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var payloadType = returnType.inner();
|
||||||
|
final var actualType = expressionAnalyzer.analyzeExpression(
|
||||||
|
okExpr.value(),
|
||||||
|
context.scope(),
|
||||||
|
payloadType,
|
||||||
|
returnType,
|
||||||
|
resultErrorName,
|
||||||
|
context.receiverType(),
|
||||||
|
context.model(),
|
||||||
|
diagnostics,
|
||||||
|
ExprUse.VALUE,
|
||||||
|
true,
|
||||||
|
this::analyzeNestedBlock).type();
|
||||||
|
if (!typeOps.compatible(actualType, payloadType)) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_RESULT_OK_PAYLOAD_MISMATCH.name(),
|
||||||
|
"Payload in 'ok(...)' is incompatible with result payload type",
|
||||||
|
okExpr.value().span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void analyzeReturnErr(
|
||||||
|
final PbsAst.ErrExpr errExpr,
|
||||||
|
final PbsFlowBodyContext context) {
|
||||||
|
final var returnType = context.returnType();
|
||||||
|
final var resultErrorName = context.resultErrorName();
|
||||||
|
final var model = context.model();
|
||||||
|
final var diagnostics = context.diagnostics();
|
||||||
|
if (returnType.kind() != Kind.RESULT || resultErrorName == null) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_RESULT_FLOW_INVALID_POSITION.name(),
|
||||||
|
"'err(...)' is only allowed when returning from a result callable",
|
||||||
|
errExpr.span());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matchesTargetError(errExpr.errorPath(), resultErrorName, model)) {
|
||||||
|
Diagnostics.error(diagnostics,
|
||||||
|
PbsSemanticsErrors.E_SEM_RESULT_ERROR_LABEL_INVALID.name(),
|
||||||
|
"Error label in 'err(...)' does not match enclosing result error type",
|
||||||
|
errExpr.errorPath().span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsAst.Expression unwrapGroup(final PbsAst.Expression expression) {
|
||||||
|
if (expression instanceof PbsAst.GroupExpr groupExpr) {
|
||||||
|
return unwrapGroup(groupExpr.expression());
|
||||||
|
}
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user