diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowBodyAnalyzer.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowBodyAnalyzer.java new file mode 100644 index 00000000..55f67d1b --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowBodyAnalyzer.java @@ -0,0 +1,300 @@ +package p.studio.compiler.pbs.semantics; + +import p.studio.compiler.pbs.ast.PbsAst; +import p.studio.compiler.source.diagnostics.DiagnosticSink; +import p.studio.utilities.structures.ReadOnlyList; +import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.ExprUse; +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 PbsFlowBodyAnalyzer { + private final PbsFlowTypeOps typeOps = new PbsFlowTypeOps(); + private final PbsFlowExpressionAnalyzer expressionAnalyzer = new PbsFlowExpressionAnalyzer(typeOps); + + public void validate(final PbsAst.File ast, final DiagnosticSink diagnostics) { + final var model = Model.from(ast); + + for (final var topDecl : ast.topDecls()) { + if (topDecl instanceof PbsAst.FunctionDecl functionDecl) { + validateCallableBody( + functionDecl.parameters(), + functionDecl.body(), + typeOps.callableReturnType(functionDecl.returnKind(), functionDecl.returnType(), functionDecl.resultErrorType(), model), + functionDecl.resultErrorType() == null ? null : functionDecl.resultErrorType().name(), + null, + model, + diagnostics); + continue; + } + if (topDecl instanceof PbsAst.StructDecl structDecl) { + final var receiverType = TypeView.struct(structDecl.name()); + for (final var method : structDecl.methods()) { + validateCallableBody( + method.parameters(), + method.body(), + typeOps.callableReturnType(method.returnKind(), method.returnType(), method.resultErrorType(), model), + method.resultErrorType() == null ? null : method.resultErrorType().name(), + receiverType, + model, + diagnostics); + } + for (final var ctor : structDecl.ctors()) { + validateCallableBody( + ctor.parameters(), + ctor.body(), + TypeView.unit(), + null, + receiverType, + model, + diagnostics); + } + continue; + } + if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) { + final var receiverType = TypeView.service(serviceDecl.name()); + for (final var method : serviceDecl.methods()) { + validateCallableBody( + method.parameters(), + method.body(), + typeOps.callableReturnType(method.returnKind(), method.returnType(), method.resultErrorType(), model), + method.resultErrorType() == null ? null : method.resultErrorType().name(), + receiverType, + model, + diagnostics); + } + } + } + } + + private void validateCallableBody( + final ReadOnlyList parameters, + final PbsAst.Block body, + final TypeView returnType, + final String resultErrorName, + final TypeView receiverType, + final Model model, + final DiagnosticSink diagnostics) { + final var scope = new Scope(); + for (final var parameter : parameters) { + scope.bind(parameter.name(), typeOps.typeFromTypeRef(parameter.typeRef(), model, receiverType)); + } + analyzeBlock(body, scope, returnType, resultErrorName, receiverType, model, diagnostics, true); + } + + private TypeView analyzeBlock( + final PbsAst.Block block, + final Scope outerScope, + final TypeView returnType, + final String resultErrorName, + final TypeView receiverType, + final Model model, + final DiagnosticSink diagnostics, + final boolean valueContext) { + final var scope = outerScope.copy(); + for (final var statement : block.statements()) { + analyzeStatement(statement, scope, returnType, resultErrorName, receiverType, model, diagnostics); + } + if (block.tailExpression() == null) { + return TypeView.unit(); + } + return expressionAnalyzer.analyzeExpression( + block.tailExpression(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + valueContext, + this::analyzeBlock).type(); + } + + private void analyzeStatement( + final PbsAst.Statement statement, + final Scope scope, + final TypeView returnType, + final String resultErrorName, + final TypeView receiverType, + final Model model, + final DiagnosticSink 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); + return; + } + if (statement instanceof PbsAst.ReturnStatement returnStatement) { + if (returnStatement.value() != null) { + expressionAnalyzer.analyzeExpression( + returnStatement.value(), + scope, + returnType, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true, + this::analyzeBlock); + } + 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( + PbsSemanticsErrors.E_SEM_IF_NON_BOOL_CONDITION.name(), + "If statement condition must have bool type", + ifStatement.condition().span()); + } + analyzeBlock(ifStatement.thenBlock(), scope, returnType, resultErrorName, receiverType, model, diagnostics, false); + if (ifStatement.elseBlock() != null) { + analyzeBlock(ifStatement.elseBlock(), scope, returnType, resultErrorName, receiverType, model, diagnostics, false); + } + if (ifStatement.elseIf() != null) { + analyzeStatement(ifStatement.elseIf(), scope, returnType, resultErrorName, receiverType, model, diagnostics); + } + return; + } + if (statement instanceof PbsAst.ForStatement( + String iteratorName, PbsAst.TypeRef type, PbsAst.Expression fromExpression, + PbsAst.Expression untilExpression, PbsAst.Expression stepExpression, PbsAst.Block body, + p.studio.compiler.source.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( + 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( + 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, bodyScope, returnType, resultErrorName, receiverType, model, diagnostics, 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( + PbsSemanticsErrors.E_SEM_WHILE_NON_BOOL_CONDITION.name(), + "While condition must have bool type", + whileStatement.condition().span()); + } + analyzeBlock(whileStatement.body(), scope, returnType, resultErrorName, receiverType, model, diagnostics, 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) { + expressionAnalyzer.analyzeExpression( + assignStatement.value(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true, + this::analyzeBlock); + } + } +} 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 new file mode 100644 index 00000000..96849190 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowExpressionAnalyzer.java @@ -0,0 +1,1076 @@ +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.DiagnosticSink; +import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.CallableSymbol; +import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.ExprResult; +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.TupleField; +import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.TypeView; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +final class PbsFlowExpressionAnalyzer { + @FunctionalInterface + interface BlockAnalyzer { + TypeView analyze( + PbsAst.Block block, + Scope outerScope, + TypeView returnType, + String resultErrorName, + TypeView receiverType, + Model model, + DiagnosticSink diagnostics, + boolean valueContext); + } + + private final PbsFlowTypeOps typeOps; + private BlockAnalyzer blockAnalyzer; + + PbsFlowExpressionAnalyzer(final PbsFlowTypeOps typeOps) { + this.typeOps = typeOps; + } + + ExprResult analyzeExpression( + final PbsAst.Expression expression, + final Scope scope, + final TypeView expectedType, + final TypeView returnType, + final String resultErrorName, + final TypeView receiverType, + final Model model, + final DiagnosticSink diagnostics, + final ExprUse use, + final boolean valueContext, + final BlockAnalyzer blockAnalyzer) { + final var previousBlockAnalyzer = this.blockAnalyzer; + this.blockAnalyzer = blockAnalyzer; + try { + return analyzeExpressionInternal( + expression, + scope, + expectedType, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + use, + valueContext); + } finally { + this.blockAnalyzer = previousBlockAnalyzer; + } + } + private ExprResult analyzeExpressionInternal( + final PbsAst.Expression expression, + final Scope scope, + final TypeView expectedType, + final TypeView returnType, + final String resultErrorName, + final TypeView receiverType, + final Model model, + final DiagnosticSink diagnostics, + final ExprUse use, + final boolean valueContext) { + if (expression instanceof PbsAst.IntLiteralExpr) { + return ExprResult.type(TypeView.intType()); + } + if (expression instanceof PbsAst.FloatLiteralExpr) { + return ExprResult.type(TypeView.floatType()); + } + if (expression instanceof PbsAst.BoundedLiteralExpr) { + return ExprResult.type(TypeView.intType()); + } + if (expression instanceof PbsAst.StringLiteralExpr) { + return ExprResult.type(TypeView.str()); + } + if (expression instanceof PbsAst.BoolLiteralExpr) { + return ExprResult.type(TypeView.bool()); + } + if (expression instanceof PbsAst.UnitExpr) { + return ExprResult.type(TypeView.unit()); + } + if (expression instanceof PbsAst.ThisExpr) { + return ExprResult.type(receiverType == null ? TypeView.unknown() : receiverType); + } + if (expression instanceof PbsAst.IdentifierExpr identifierExpr) { + final var localType = scope.resolve(identifierExpr.name()); + if (localType != null) { + return ExprResult.type(localType); + } + + final var serviceType = model.serviceSingletons.get(identifierExpr.name()); + if (serviceType != null) { + return ExprResult.type(serviceType); + } + + final var constType = model.constTypes.get(identifierExpr.name()); + if (constType != null) { + return ExprResult.type(constType); + } + + final var callbackSignature = model.callbacks.get(identifierExpr.name()); + if (callbackSignature != null) { + return ExprResult.type(TypeView.callback(identifierExpr.name(), callbackSignature.inputTypes(), callbackSignature.outputType())); + } + + final var callables = model.topLevelCallables.get(identifierExpr.name()); + if (callables != null && !callables.isEmpty()) { + return ExprResult.callables(callables, false); + } + + if (model.enums.containsKey(identifierExpr.name())) { + return ExprResult.type(TypeView.typeRef(identifierExpr.name())); + } + + return ExprResult.type(TypeView.unknown()); + } + if (expression instanceof PbsAst.GroupExpr groupExpr) { + return analyzeExpressionInternal( + groupExpr.expression(), + scope, + expectedType, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + use, + valueContext); + } + if (expression instanceof PbsAst.TupleExpr tupleExpr) { + final var fields = new ArrayList(tupleExpr.items().size()); + for (final var item : tupleExpr.items()) { + final var value = analyzeExpressionInternal( + item.expression(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true).type(); + fields.add(new TupleField(item.label(), value)); + } + return ExprResult.type(TypeView.tuple(fields)); + } + if (expression instanceof PbsAst.BlockExpr blockExpr) { + return ExprResult.type(blockAnalyzer.analyze( + blockExpr.block(), + scope, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + valueContext)); + } + if (expression instanceof PbsAst.UnaryExpr unaryExpr) { + final var operand = analyzeExpressionInternal( + unaryExpr.expression(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true).type(); + if ("!".equals(unaryExpr.operator()) || "not".equals(unaryExpr.operator())) { + return ExprResult.type(TypeView.bool()); + } + if ("-".equals(unaryExpr.operator()) && (typeOps.isInt(operand) || typeOps.isFloat(operand))) { + return ExprResult.type(operand); + } + return ExprResult.type(TypeView.unknown()); + } + if (expression instanceof PbsAst.BinaryExpr binaryExpr) { + final var left = analyzeExpressionInternal( + binaryExpr.left(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true).type(); + final var right = analyzeExpressionInternal( + binaryExpr.right(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true).type(); + return ExprResult.type(typeOps.inferBinaryResult(binaryExpr.operator(), left, right)); + } + if (expression instanceof PbsAst.MemberExpr memberExpr) { + return analyzeMemberExpression( + memberExpr, + scope, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + use); + } + if (expression instanceof PbsAst.CallExpr callExpr) { + return analyzeCallExpression( + callExpr, + scope, + expectedType, + returnType, + resultErrorName, + receiverType, + model, + diagnostics); + } + if (expression instanceof PbsAst.ApplyExpr applyExpr) { + return analyzeApplyExpression( + applyExpr, + scope, + expectedType, + returnType, + resultErrorName, + receiverType, + model, + diagnostics); + } + if (expression instanceof PbsAst.IfExpr ifExpr) { + final var condition = analyzeExpressionInternal( + ifExpr.condition(), + scope, + TypeView.bool(), + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true).type(); + if (!typeOps.isBool(condition)) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_IF_NON_BOOL_CONDITION.name(), + "If expression condition must have bool type", + ifExpr.condition().span()); + } + + final var thenType = blockAnalyzer.analyze(ifExpr.thenBlock(), scope, returnType, resultErrorName, receiverType, model, diagnostics, true); + final var elseType = analyzeExpressionInternal( + ifExpr.elseExpression(), + scope, + expectedType, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true).type(); + + if (!typeOps.compatible(thenType, elseType)) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_IF_BRANCH_TYPE_MISMATCH.name(), + "If expression branches must have compatible types", + ifExpr.span()); + return ExprResult.type(TypeView.unknown()); + } + return ExprResult.type(thenType); + } + if (expression instanceof PbsAst.SwitchExpr switchExpr) { + return ExprResult.type(analyzeSwitchExpression( + switchExpr, + scope, + expectedType, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + valueContext)); + } + if (expression instanceof PbsAst.ElseExpr elseExpr) { + final var optional = analyzeExpressionInternal( + elseExpr.optionalExpression(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true).type(); + if (optional.kind() != Kind.OPTIONAL) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_ELSE_NON_OPTIONAL_LEFT.name(), + "Left operand of 'else' must have optional type", + elseExpr.optionalExpression().span()); + analyzeExpressionInternal( + elseExpr.fallbackExpression(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true); + return ExprResult.type(TypeView.unknown()); + } + + final var payload = optional.inner(); + final var fallback = analyzeExpressionInternal( + elseExpr.fallbackExpression(), + scope, + payload, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true).type(); + if (!typeOps.compatible(fallback, payload)) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_ELSE_FALLBACK_TYPE_MISMATCH.name(), + "Fallback expression in 'else' must match optional payload type", + elseExpr.fallbackExpression().span()); + } + return ExprResult.type(payload); + } + if (expression instanceof PbsAst.PropagateExpr propagateExpr) { + final var source = analyzeExpressionInternal( + propagateExpr.expression(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true).type(); + if (source.kind() != Kind.RESULT) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_RESULT_PROPAGATE_NON_RESULT.name(), + "Propagation operator '!' requires result type", + propagateExpr.expression().span()); + return ExprResult.type(TypeView.unknown()); + } + final var sourceError = source.errorType(); + if (resultErrorName == null || sourceError == null || !resultErrorName.equals(sourceError.name())) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_RESULT_PROPAGATE_ERROR_MISMATCH.name(), + "Propagation operator '!' requires matching result error type in enclosing callable", + propagateExpr.span()); + } + return ExprResult.type(source.inner()); + } + if (expression instanceof PbsAst.HandleExpr handleExpr) { + return ExprResult.type(analyzeHandleExpression( + handleExpr, + scope, + returnType, + resultErrorName, + receiverType, + model, + diagnostics)); + } + if (expression instanceof PbsAst.NoneExpr noneExpr) { + if (expectedType == null || expectedType.kind() != Kind.OPTIONAL) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_NONE_WITHOUT_EXPECTED_OPTIONAL.name(), + "'none' requires an expected optional type context", + noneExpr.span()); + return ExprResult.type(TypeView.unknown()); + } + return ExprResult.type(expectedType); + } + if (expression instanceof PbsAst.SomeExpr someExpr) { + final var payloadExpected = expectedType != null && expectedType.kind() == Kind.OPTIONAL + ? expectedType.inner() + : null; + final var payload = analyzeExpressionInternal( + someExpr.value(), + scope, + payloadExpected, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true).type(); + return ExprResult.type(TypeView.optional(payloadExpected == null ? payload : payloadExpected)); + } + if (expression instanceof PbsAst.BindExpr bindExpr) { + final var contextType = analyzeExpressionInternal( + bindExpr.contextExpression(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true).type(); + if (expectedType == null || expectedType.kind() != Kind.CALLBACK) { + return ExprResult.type(TypeView.unknown()); + } + + final var topLevel = model.topLevelCallables.get(bindExpr.functionName()); + if (topLevel == null || topLevel.isEmpty()) { + return ExprResult.type(expectedType); + } + final var compatible = new ArrayList(); + for (final var candidate : topLevel) { + if (candidate.inputTypes().size() != expectedType.callbackInputs().size() + 1) { + continue; + } + if (!typeOps.compatible(contextType, candidate.inputTypes().getFirst())) { + continue; + } + var ok = true; + for (int i = 0; i < expectedType.callbackInputs().size(); i++) { + if (!typeOps.compatible(expectedType.callbackInputs().get(i), candidate.inputTypes().get(i + 1))) { + ok = false; + break; + } + } + if (!ok) { + continue; + } + if (!typeOps.compatible(candidate.outputType(), expectedType.callbackOutput())) { + continue; + } + compatible.add(candidate); + } + if (compatible.size() > 1) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_APPLY_AMBIGUOUS_OVERLOAD.name(), + "Bind target resolves ambiguously for callback type", + bindExpr.span()); + } else if (compatible.isEmpty()) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_APPLY_UNRESOLVED_OVERLOAD.name(), + "Bind target does not resolve for callback type", + bindExpr.span()); + } + return ExprResult.type(expectedType); + } + if (expression instanceof PbsAst.NewExpr newExpr) { + if (model.structs.containsKey(newExpr.typeName())) { + return ExprResult.type(TypeView.struct(newExpr.typeName())); + } + return ExprResult.type(TypeView.unknown()); + } + if (expression instanceof PbsAst.AsExpr asExpr) { + analyzeExpressionInternal( + asExpr.expression(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true); + if (model.contracts.containsKey(asExpr.contractName())) { + return ExprResult.type(TypeView.contract(asExpr.contractName())); + } + return ExprResult.type(TypeView.unknown()); + } + + if (expression instanceof PbsAst.OkExpr okExpr) { + analyzeExpressionInternal( + okExpr.value(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true); + return ExprResult.type(TypeView.unknown()); + } + if (expression instanceof PbsAst.ErrExpr errExpr) { + return ExprResult.type(TypeView.unknown()); + } + + return ExprResult.type(TypeView.unknown()); + } + + private ExprResult analyzeCallExpression( + final PbsAst.CallExpr callExpr, + final Scope scope, + final TypeView expectedType, + final TypeView returnType, + final String resultErrorName, + final TypeView receiverType, + final Model model, + final DiagnosticSink diagnostics) { + final var callee = analyzeExpressionInternal( + callExpr.callee(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.CALL_TARGET, + true); + + final TypeView argumentType; + if (callExpr.arguments().isEmpty()) { + argumentType = TypeView.unit(); + } else if (callExpr.arguments().size() == 1) { + argumentType = analyzeExpressionInternal( + callExpr.arguments().getFirst(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true).type(); + } else { + final var fields = new ArrayList(callExpr.arguments().size()); + for (final var argument : callExpr.arguments()) { + final var itemType = analyzeExpressionInternal( + argument, + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true).type(); + fields.add(new TupleField(null, itemType)); + } + argumentType = TypeView.tuple(fields); + } + + return resolveCallableApplication( + callExpr.span(), + callExpr.callee().span(), + callee, + argumentType, + expectedType, + diagnostics); + } + + private ExprResult analyzeApplyExpression( + final PbsAst.ApplyExpr applyExpr, + final Scope scope, + final TypeView expectedType, + final TypeView returnType, + final String resultErrorName, + final TypeView receiverType, + final Model model, + final DiagnosticSink diagnostics) { + final var callee = analyzeExpressionInternal( + applyExpr.callee(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.APPLY_TARGET, + true); + final var argument = analyzeExpressionInternal( + applyExpr.argument(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true).type(); + + return resolveCallableApplication( + applyExpr.span(), + applyExpr.callee().span(), + callee, + argument, + expectedType, + diagnostics); + } + + private ExprResult resolveCallableApplication( + final Span wholeSpan, + final Span calleeSpan, + final ExprResult callee, + final TypeView argumentType, + final TypeView expectedType, + final DiagnosticSink diagnostics) { + final var candidates = callee.callables(); + if (candidates.isEmpty()) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_APPLY_NON_CALLABLE_TARGET.name(), + "Apply/call target is not callable", + calleeSpan); + return ExprResult.type(TypeView.unknown()); + } + + final var compatible = new ArrayList(); + for (final var candidate : candidates) { + if (typeOps.inputCompatible(candidate.inputTypes(), argumentType)) { + compatible.add(candidate); + } + } + + if (compatible.isEmpty()) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_APPLY_UNRESOLVED_OVERLOAD.name(), + "No callable overload matches the provided argument", + wholeSpan); + return ExprResult.type(TypeView.unknown()); + } + + if (compatible.size() > 1 && expectedType != null) { + final var narrowed = compatible.stream() + .filter(candidate -> typeOps.compatible(candidate.outputType(), expectedType)) + .toList(); + if (narrowed.size() == 1) { + return ExprResult.type(narrowed.getFirst().outputType()); + } + if (!narrowed.isEmpty()) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_APPLY_AMBIGUOUS_OVERLOAD.name(), + "Callable overload resolution remains ambiguous", + wholeSpan); + return ExprResult.type(TypeView.unknown()); + } + } + + if (compatible.size() > 1) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_APPLY_AMBIGUOUS_OVERLOAD.name(), + "Callable overload resolution is ambiguous", + wholeSpan); + return ExprResult.type(TypeView.unknown()); + } + + return ExprResult.type(compatible.getFirst().outputType()); + } + + private ExprResult analyzeMemberExpression( + final PbsAst.MemberExpr memberExpr, + final Scope scope, + final TypeView returnType, + final String resultErrorName, + final TypeView receiverType, + final Model model, + final DiagnosticSink diagnostics, + final ExprUse use) { + final var receiver = analyzeExpressionInternal( + memberExpr.receiver(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true).type(); + + if (receiver.kind() == Kind.TYPE_REF) { + final var enumCases = model.enums.get(receiver.name()); + if (enumCases != null && enumCases.contains(memberExpr.memberName())) { + return ExprResult.type(TypeView.enumType(receiver.name())); + } + diagnostics.error( + PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), + "Invalid type member access", + memberExpr.span()); + return ExprResult.type(TypeView.unknown()); + } + + if (receiver.kind() == Kind.TUPLE) { + if (receiver.tupleFields().size() <= 1) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), + "Member projection is not defined for single-slot carrier values", + memberExpr.span()); + return ExprResult.type(TypeView.unknown()); + } + for (final var field : receiver.tupleFields()) { + if (memberExpr.memberName().equals(field.label())) { + return ExprResult.type(field.type()); + } + } + diagnostics.error( + PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), + "Tuple label '%s' does not exist".formatted(memberExpr.memberName()), + memberExpr.span()); + return ExprResult.type(TypeView.unknown()); + } + + if (receiver.kind() == Kind.STRUCT) { + final var struct = model.structs.get(receiver.name()); + if (struct == null) { + return ExprResult.type(TypeView.unknown()); + } + final var fieldType = struct.fields().get(memberExpr.memberName()); + if (fieldType != null) { + return ExprResult.type(fieldType); + } + final var methods = struct.methods().get(memberExpr.memberName()); + if (methods != null && !methods.isEmpty()) { + if (use == ExprUse.VALUE) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_BARE_METHOD_EXTRACTION.name(), + "Bare method extraction is not allowed in PBS core", + memberExpr.span()); + return ExprResult.type(TypeView.unknown()); + } + return ExprResult.callables(methods, true); + } + diagnostics.error( + PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), + "Struct member '%s' does not exist".formatted(memberExpr.memberName()), + memberExpr.span()); + return ExprResult.type(TypeView.unknown()); + } + + if (receiver.kind() == Kind.SERVICE) { + final var service = model.services.get(receiver.name()); + if (service == null) { + return ExprResult.type(TypeView.unknown()); + } + final var methods = service.methods().get(memberExpr.memberName()); + if (methods != null && !methods.isEmpty()) { + if (use == ExprUse.VALUE) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_BARE_METHOD_EXTRACTION.name(), + "Bare method extraction is not allowed in PBS core", + memberExpr.span()); + return ExprResult.type(TypeView.unknown()); + } + return ExprResult.callables(methods, true); + } + diagnostics.error( + PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), + "Service member '%s' does not exist".formatted(memberExpr.memberName()), + memberExpr.span()); + return ExprResult.type(TypeView.unknown()); + } + + if (receiver.kind() == Kind.CONTRACT) { + final var contract = model.contracts.get(receiver.name()); + if (contract == null) { + return ExprResult.type(TypeView.unknown()); + } + final var methods = contract.methods().get(memberExpr.memberName()); + if (methods != null && !methods.isEmpty()) { + if (use == ExprUse.VALUE) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_BARE_METHOD_EXTRACTION.name(), + "Bare method extraction is not allowed in PBS core", + memberExpr.span()); + return ExprResult.type(TypeView.unknown()); + } + return ExprResult.callables(methods, true); + } + diagnostics.error( + PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), + "Contract member '%s' does not exist".formatted(memberExpr.memberName()), + memberExpr.span()); + return ExprResult.type(TypeView.unknown()); + } + + diagnostics.error( + PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), + "Invalid member access target", + memberExpr.span()); + return ExprResult.type(TypeView.unknown()); + } + + private TypeView analyzeSwitchExpression( + final PbsAst.SwitchExpr switchExpr, + final Scope scope, + final TypeView expectedType, + final TypeView returnType, + final String resultErrorName, + final TypeView receiverType, + final Model model, + final DiagnosticSink diagnostics, + final boolean valueContext) { + final var selector = analyzeExpressionInternal( + switchExpr.selector(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true).type(); + + final var selectorComparable = typeOps.isScalarComparable(selector) || selector.kind() == Kind.ENUM; + if (!selectorComparable) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_SWITCH_SELECTOR_INVALID.name(), + "Switch selector type is not supported", + switchExpr.selector().span()); + } + + final var seenPatterns = new HashSet(); + boolean hasWildcard = false; + TypeView armType = null; + + for (final var arm : switchExpr.arms()) { + final var patternKey = switchPatternKey(arm.pattern()); + if (arm.pattern() instanceof PbsAst.WildcardSwitchPattern) { + hasWildcard = true; + } else if (!seenPatterns.add(patternKey)) { + diagnostics.error( + 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( + PbsSemanticsErrors.E_SEM_SWITCH_PATTERN_TYPE_MISMATCH.name(), + "Switch pattern is not compatible with selector type", + arm.pattern().span()); + } + + final var currentArmType = blockAnalyzer.analyze( + arm.block(), + scope, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + true); + if (armType == null) { + armType = currentArmType; + } else if (!typeOps.compatible(currentArmType, armType)) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_SWITCH_ARM_TYPE_MISMATCH.name(), + "Switch arm block types must be compatible", + arm.span()); + } + } + + if (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( + PbsSemanticsErrors.E_SEM_SWITCH_NON_EXHAUSTIVE.name(), + "Switch expression in value position must be exhaustive", + switchExpr.span()); + } + } + + if (armType == null) { + return TypeView.unit(); + } + if (expectedType != null && typeOps.compatible(armType, expectedType)) { + return 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)) { + diagnostics.error( + 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 Scope scope, + final TypeView returnType, + final String resultErrorName, + final TypeView receiverType, + final Model model, + final DiagnosticSink diagnostics) { + final var sourceType = analyzeExpressionInternal( + handleExpr.value(), + scope, + null, + returnType, + resultErrorName, + receiverType, + model, + diagnostics, + ExprUse.VALUE, + true).type(); + if (sourceType.kind() != Kind.RESULT) { + diagnostics.error( + PbsSemanticsErrors.E_SEM_HANDLE_NON_RESULT.name(), + "Handle requires result expression", + handleExpr.value().span()); + return TypeView.unknown(); + } + if (resultErrorName == null) { + diagnostics.error( + 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 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( + 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( + 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( + PbsSemanticsErrors.E_SEM_HANDLE_ERROR_MISMATCH.name(), + "Handle remap target must match enclosing callable result error type", + arm.remapTarget().span()); + } + continue; + } + blockAnalyzer.analyze(arm.block(), scope, returnType, resultErrorName, receiverType, model, diagnostics, true); + } + + if (!hasWildcard && !sourceCases.isEmpty() && !matchedCases.containsAll(sourceCases)) { + diagnostics.error( + 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 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); + } + +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticSupport.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticSupport.java new file mode 100644 index 00000000..2ca1a8d6 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticSupport.java @@ -0,0 +1,367 @@ +package p.studio.compiler.pbs.semantics; + +import lombok.experimental.UtilityClass; +import p.studio.compiler.pbs.ast.PbsAst; +import p.studio.compiler.source.Span; +import p.studio.utilities.structures.ReadOnlyList; + +import java.util.*; + +@UtilityClass +final class PbsFlowSemanticSupport { + record ExprResult( + TypeView type, + List callables, + boolean methodTarget) { + static ExprResult type(final TypeView type) { + return new ExprResult(type, List.of(), false); + } + + static ExprResult callables(final List callables, final boolean methodTarget) { + return new ExprResult(TypeView.unknown(), List.copyOf(callables), methodTarget); + } + } + + enum ExprUse { + VALUE, + CALL_TARGET, + APPLY_TARGET + } + + enum Kind { + UNKNOWN, + UNIT, + INT, + FLOAT, + BOOL, + STR, + STRUCT, + SERVICE, + CONTRACT, + CALLBACK, + ENUM, + ERROR, + OPTIONAL, + RESULT, + TUPLE, + TYPE_REF + } + + record TupleField( + String label, + TypeView type) { + } + + record TypeView( + Kind kind, + String name, + List tupleFields, + TypeView inner, + TypeView errorType, + List callbackInputs, + TypeView callbackOutput) { + static TypeView unknown() { + return new TypeView(Kind.UNKNOWN, null, List.of(), null, null, List.of(), null); + } + + static TypeView unit() { + return new TypeView(Kind.UNIT, "unit", List.of(), null, null, List.of(), null); + } + + static TypeView intType() { + return new TypeView(Kind.INT, "int", List.of(), null, null, List.of(), null); + } + + static TypeView floatType() { + return new TypeView(Kind.FLOAT, "float", List.of(), null, null, List.of(), null); + } + + static TypeView bool() { + return new TypeView(Kind.BOOL, "bool", List.of(), null, null, List.of(), null); + } + + static TypeView str() { + return new TypeView(Kind.STR, "str", List.of(), null, null, List.of(), null); + } + + static TypeView struct(final String name) { + return new TypeView(Kind.STRUCT, name, List.of(), null, null, List.of(), null); + } + + static TypeView service(final String name) { + return new TypeView(Kind.SERVICE, name, List.of(), null, null, List.of(), null); + } + + static TypeView contract(final String name) { + return new TypeView(Kind.CONTRACT, name, List.of(), null, null, List.of(), null); + } + + static TypeView callback( + final String name, + final List inputTypes, + final TypeView outputType) { + return new TypeView(Kind.CALLBACK, name, List.of(), null, null, List.copyOf(inputTypes), outputType); + } + + static TypeView enumType(final String name) { + return new TypeView(Kind.ENUM, name, List.of(), null, null, List.of(), null); + } + + static TypeView error(final String name) { + return new TypeView(Kind.ERROR, name, List.of(), null, null, List.of(), null); + } + + static TypeView optional(final TypeView inner) { + return new TypeView(Kind.OPTIONAL, "optional", List.of(), inner, null, List.of(), null); + } + + static TypeView result(final TypeView error, final TypeView payload) { + return new TypeView(Kind.RESULT, "result", List.of(), payload, error, List.of(), null); + } + + static TypeView tuple(final List fields) { + return new TypeView(Kind.TUPLE, "tuple", List.copyOf(fields), null, null, List.of(), null); + } + + static TypeView typeRef(final String name) { + return new TypeView(Kind.TYPE_REF, name, List.of(), null, null, List.of(), null); + } + } + + record CallableSymbol( + String name, + List inputTypes, + TypeView outputType, + Span span) { + } + + record CallbackSignature( + List inputTypes, + TypeView outputType) { + } + + record StructInfo( + Map fields, + Map> methods) { + } + + record ServiceInfo( + Map> methods) { + } + + record ContractInfo( + Map> methods) { + } + + static final class Model { + final Map> topLevelCallables = new HashMap<>(); + final Map structs = new HashMap<>(); + final Map services = new HashMap<>(); + final Map contracts = new HashMap<>(); + final Map callbacks = new HashMap<>(); + final Map> enums = new HashMap<>(); + final Map> errors = new HashMap<>(); + final Map constTypes = new HashMap<>(); + final Map serviceSingletons = new HashMap<>(); + + static Model from(final PbsAst.File ast) { + final var model = new Model(); + for (final var topDecl : ast.topDecls()) { + if (topDecl instanceof PbsAst.StructDecl structDecl) { + final var fields = new HashMap(); + for (final var field : structDecl.fields()) { + fields.put(field.name(), model.typeFrom(field.typeRef())); + } + final var methods = new HashMap>(); + for (final var method : structDecl.methods()) { + methods.computeIfAbsent(method.name(), ignored -> new ArrayList<>()) + .add(model.callableFrom( + method.name(), + method.parameters(), + method.returnKind(), + method.returnType(), + method.resultErrorType(), + method.span())); + } + model.structs.put(structDecl.name(), new StructInfo(fields, methods)); + continue; + } + if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) { + final var methods = new HashMap>(); + for (final var method : serviceDecl.methods()) { + methods.computeIfAbsent(method.name(), ignored -> new ArrayList<>()) + .add(model.callableFrom( + method.name(), + method.parameters(), + method.returnKind(), + method.returnType(), + method.resultErrorType(), + method.span())); + } + model.services.put(serviceDecl.name(), new ServiceInfo(methods)); + model.serviceSingletons.put(serviceDecl.name(), TypeView.service(serviceDecl.name())); + continue; + } + if (topDecl instanceof PbsAst.ContractDecl contractDecl) { + final var methods = new HashMap>(); + for (final var signature : contractDecl.signatures()) { + methods.computeIfAbsent(signature.name(), ignored -> new ArrayList<>()) + .add(model.callableFrom( + signature.name(), + signature.parameters(), + signature.returnKind(), + signature.returnType(), + signature.resultErrorType(), + signature.span())); + } + model.contracts.put(contractDecl.name(), new ContractInfo(methods)); + continue; + } + if (topDecl instanceof PbsAst.FunctionDecl functionDecl) { + model.topLevelCallables.computeIfAbsent(functionDecl.name(), ignored -> new ArrayList<>()) + .add(model.callableFrom( + functionDecl.name(), + functionDecl.parameters(), + functionDecl.returnKind(), + functionDecl.returnType(), + functionDecl.resultErrorType(), + functionDecl.span())); + continue; + } + if (topDecl instanceof PbsAst.CallbackDecl( + String name, ReadOnlyList parameters, PbsAst.ReturnKind returnKind, + PbsAst.TypeRef returnType, PbsAst.TypeRef resultErrorType, Span span + )) { + final var symbol = model.callableFrom( + name, + parameters, + returnKind, + returnType, + resultErrorType, + span); + model.callbacks.put(name, new CallbackSignature(symbol.inputTypes(), symbol.outputType())); + continue; + } + if (topDecl instanceof PbsAst.EnumDecl enumDecl) { + final var cases = new HashSet(); + for (final var enumCase : enumDecl.cases()) { + cases.add(enumCase.name()); + } + model.enums.put(enumDecl.name(), cases); + continue; + } + if (topDecl instanceof PbsAst.ErrorDecl errorDecl) { + model.errors.put(errorDecl.name(), new HashSet<>(errorDecl.cases().asList())); + continue; + } + if (topDecl instanceof PbsAst.ConstDecl constDecl && constDecl.explicitType() != null) { + model.constTypes.put(constDecl.name(), model.typeFrom(constDecl.explicitType())); + } + } + return model; + } + + private CallableSymbol callableFrom( + final String name, + final ReadOnlyList parameters, + final PbsAst.ReturnKind returnKind, + final PbsAst.TypeRef returnType, + final PbsAst.TypeRef resultErrorType, + final Span span) { + final var input = new ArrayList(parameters.size()); + for (final var parameter : parameters) { + input.add(typeFrom(parameter.typeRef())); + } + return new CallableSymbol(name, input, callableReturn(returnKind, returnType, resultErrorType), span); + } + + private TypeView callableReturn( + final PbsAst.ReturnKind returnKind, + final PbsAst.TypeRef returnType, + final PbsAst.TypeRef resultErrorType) { + return switch (returnKind) { + case INFERRED_UNIT, EXPLICIT_UNIT -> TypeView.unit(); + case PLAIN -> collapse(typeFrom(returnType)); + case RESULT -> TypeView.result(typeFrom(resultErrorType), collapse(typeFrom(returnType))); + }; + } + + private TypeView collapse(final TypeView type) { + if (type.kind() == Kind.TUPLE && type.tupleFields().size() == 1) { + return type.tupleFields().getFirst().type(); + } + return type; + } + + private TypeView typeFrom(final PbsAst.TypeRef typeRef) { + if (typeRef == null) { + return TypeView.unit(); + } + return switch (typeRef.kind()) { + case UNIT -> TypeView.unit(); + case SELF, ERROR -> TypeView.unknown(); + case SIMPLE -> { + if ("int".equals(typeRef.name())) { + yield TypeView.intType(); + } + if ("float".equals(typeRef.name())) { + yield TypeView.floatType(); + } + if ("bool".equals(typeRef.name())) { + yield TypeView.bool(); + } + if ("str".equals(typeRef.name())) { + yield TypeView.str(); + } + if (structs.containsKey(typeRef.name())) { + yield TypeView.struct(typeRef.name()); + } + if (services.containsKey(typeRef.name())) { + yield TypeView.service(typeRef.name()); + } + if (contracts.containsKey(typeRef.name())) { + yield TypeView.contract(typeRef.name()); + } + if (enums.containsKey(typeRef.name())) { + yield TypeView.enumType(typeRef.name()); + } + if (errors.containsKey(typeRef.name())) { + yield TypeView.error(typeRef.name()); + } + final var callbackSignature = callbacks.get(typeRef.name()); + if (callbackSignature != null) { + yield TypeView.callback(typeRef.name(), callbackSignature.inputTypes(), callbackSignature.outputType()); + } + yield TypeView.unknown(); + } + case OPTIONAL -> TypeView.optional(typeFrom(typeRef.inner())); + case GROUP -> typeFrom(typeRef.inner()); + case NAMED_TUPLE -> { + final var fields = new ArrayList(typeRef.fields().size()); + for (final var field : typeRef.fields()) { + fields.add(new TupleField(field.label(), typeFrom(field.typeRef()))); + } + yield TypeView.tuple(fields); + } + }; + } + } + + static final class Scope { + private final Map names = new HashMap<>(); + + Scope copy() { + final var scope = new Scope(); + scope.names.putAll(names); + return scope; + } + + void bind(final String name, final TypeView type) { + names.put(name, type); + } + + TypeView resolve(final String name) { + return names.get(name); + } + } +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticsValidator.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticsValidator.java index d293be0f..987797d4 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticsValidator.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowSemanticsValidator.java @@ -1,1831 +1,12 @@ 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.DiagnosticSink; -import p.studio.utilities.structures.ReadOnlyList; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; public final class PbsFlowSemanticsValidator { + private final PbsFlowBodyAnalyzer flowBodyAnalyzer = new PbsFlowBodyAnalyzer(); + public void validate(final PbsAst.File ast, final DiagnosticSink diagnostics) { - final var model = Model.from(ast); - - for (final var topDecl : ast.topDecls()) { - if (topDecl instanceof PbsAst.FunctionDecl functionDecl) { - validateCallableBody( - functionDecl.parameters(), - functionDecl.body(), - callableReturnType(functionDecl.returnKind(), functionDecl.returnType(), functionDecl.resultErrorType(), model), - functionDecl.resultErrorType() == null ? null : functionDecl.resultErrorType().name(), - null, - model, - diagnostics); - continue; - } - if (topDecl instanceof PbsAst.StructDecl structDecl) { - final var receiverType = TypeView.struct(structDecl.name()); - for (final var method : structDecl.methods()) { - validateCallableBody( - method.parameters(), - method.body(), - callableReturnType(method.returnKind(), method.returnType(), method.resultErrorType(), model), - method.resultErrorType() == null ? null : method.resultErrorType().name(), - receiverType, - model, - diagnostics); - } - for (final var ctor : structDecl.ctors()) { - validateCallableBody( - ctor.parameters(), - ctor.body(), - TypeView.unit(), - null, - receiverType, - model, - diagnostics); - } - continue; - } - if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) { - final var receiverType = TypeView.service(serviceDecl.name()); - for (final var method : serviceDecl.methods()) { - validateCallableBody( - method.parameters(), - method.body(), - callableReturnType(method.returnKind(), method.returnType(), method.resultErrorType(), model), - method.resultErrorType() == null ? null : method.resultErrorType().name(), - receiverType, - model, - diagnostics); - } - } - } - } - - private void validateCallableBody( - final ReadOnlyList parameters, - final PbsAst.Block body, - final TypeView returnType, - final String resultErrorName, - final TypeView receiverType, - final Model model, - final DiagnosticSink diagnostics) { - final var scope = new Scope(); - for (final var parameter : parameters) { - scope.bind(parameter.name(), typeFromTypeRef(parameter.typeRef(), model, receiverType)); - } - analyzeBlock(body, scope, returnType, resultErrorName, receiverType, model, diagnostics, true); - } - - private TypeView analyzeBlock( - final PbsAst.Block block, - final Scope outerScope, - final TypeView returnType, - final String resultErrorName, - final TypeView receiverType, - final Model model, - final DiagnosticSink diagnostics, - final boolean valueContext) { - final var scope = outerScope.copy(); - for (final var statement : block.statements()) { - analyzeStatement(statement, scope, returnType, resultErrorName, receiverType, model, diagnostics); - } - if (block.tailExpression() == null) { - return TypeView.unit(); - } - return analyzeExpression( - block.tailExpression(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - valueContext).type(); - } - - private void analyzeStatement( - final PbsAst.Statement statement, - final Scope scope, - final TypeView returnType, - final String resultErrorName, - final TypeView receiverType, - final Model model, - final DiagnosticSink diagnostics) { - if (statement instanceof PbsAst.LetStatement letStatement) { - final var expected = letStatement.explicitType() == null - ? null - : typeFromTypeRef(letStatement.explicitType(), model, receiverType); - final var initializer = analyzeExpression( - letStatement.initializer(), - scope, - expected, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true); - scope.bind(letStatement.name(), expected == null ? initializer.type() : expected); - return; - } - if (statement instanceof PbsAst.ReturnStatement returnStatement) { - if (returnStatement.value() != null) { - analyzeExpression( - returnStatement.value(), - scope, - returnType, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true); - } - return; - } - if (statement instanceof PbsAst.IfStatement ifStatement) { - final var condition = analyzeExpression( - ifStatement.condition(), - scope, - TypeView.bool(), - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - if (!isBool(condition)) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_IF_NON_BOOL_CONDITION.name(), - "If statement condition must have bool type", - ifStatement.condition().span()); - } - analyzeBlock(ifStatement.thenBlock(), scope, returnType, resultErrorName, receiverType, model, diagnostics, false); - if (ifStatement.elseBlock() != null) { - analyzeBlock(ifStatement.elseBlock(), scope, returnType, resultErrorName, receiverType, model, diagnostics, false); - } - if (ifStatement.elseIf() != null) { - analyzeStatement(ifStatement.elseIf(), scope, returnType, resultErrorName, receiverType, model, diagnostics); - } - return; - } - if (statement instanceof PbsAst.ForStatement forStatement) { - final var iteratorType = typeFromTypeRef(forStatement.iteratorType(), model, receiverType); - final var fromType = analyzeExpression( - forStatement.fromExpression(), - scope, - iteratorType, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - final var untilType = analyzeExpression( - forStatement.untilExpression(), - scope, - iteratorType, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - if (!compatible(fromType, iteratorType) || !compatible(untilType, iteratorType)) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_FOR_TYPE_MISMATCH.name(), - "For-loop bounds must match declared iterator type", - forStatement.span()); - } - if (forStatement.stepExpression() != null) { - final var stepType = analyzeExpression( - forStatement.stepExpression(), - scope, - iteratorType, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - if (!compatible(stepType, iteratorType)) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_FOR_TYPE_MISMATCH.name(), - "For-loop step must match declared iterator type", - forStatement.stepExpression().span()); - } - } - final var bodyScope = scope.copy(); - bodyScope.bind(forStatement.iteratorName(), iteratorType); - analyzeBlock(forStatement.body(), bodyScope, returnType, resultErrorName, receiverType, model, diagnostics, false); - return; - } - if (statement instanceof PbsAst.WhileStatement whileStatement) { - final var condition = analyzeExpression( - whileStatement.condition(), - scope, - TypeView.bool(), - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - if (!isBool(condition)) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_WHILE_NON_BOOL_CONDITION.name(), - "While condition must have bool type", - whileStatement.condition().span()); - } - analyzeBlock(whileStatement.body(), scope, returnType, resultErrorName, receiverType, model, diagnostics, false); - return; - } - if (statement instanceof PbsAst.ExpressionStatement expressionStatement) { - analyzeExpression( - expressionStatement.expression(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - false); - return; - } - if (statement instanceof PbsAst.AssignStatement assignStatement) { - analyzeExpression( - assignStatement.value(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true); - } - } - - private ExprResult analyzeExpression( - final PbsAst.Expression expression, - final Scope scope, - final TypeView expectedType, - final TypeView returnType, - final String resultErrorName, - final TypeView receiverType, - final Model model, - final DiagnosticSink diagnostics, - final ExprUse use, - final boolean valueContext) { - if (expression instanceof PbsAst.IntLiteralExpr) { - return ExprResult.type(TypeView.intType()); - } - if (expression instanceof PbsAst.FloatLiteralExpr) { - return ExprResult.type(TypeView.floatType()); - } - if (expression instanceof PbsAst.BoundedLiteralExpr) { - return ExprResult.type(TypeView.intType()); - } - if (expression instanceof PbsAst.StringLiteralExpr) { - return ExprResult.type(TypeView.str()); - } - if (expression instanceof PbsAst.BoolLiteralExpr) { - return ExprResult.type(TypeView.bool()); - } - if (expression instanceof PbsAst.UnitExpr) { - return ExprResult.type(TypeView.unit()); - } - if (expression instanceof PbsAst.ThisExpr) { - return ExprResult.type(receiverType == null ? TypeView.unknown() : receiverType); - } - if (expression instanceof PbsAst.IdentifierExpr identifierExpr) { - final var localType = scope.resolve(identifierExpr.name()); - if (localType != null) { - return ExprResult.type(localType); - } - - final var serviceType = model.serviceSingletons.get(identifierExpr.name()); - if (serviceType != null) { - return ExprResult.type(serviceType); - } - - final var constType = model.constTypes.get(identifierExpr.name()); - if (constType != null) { - return ExprResult.type(constType); - } - - final var callbackSignature = model.callbacks.get(identifierExpr.name()); - if (callbackSignature != null) { - return ExprResult.type(TypeView.callback(identifierExpr.name(), callbackSignature.inputTypes(), callbackSignature.outputType())); - } - - final var callables = model.topLevelCallables.get(identifierExpr.name()); - if (callables != null && !callables.isEmpty()) { - return ExprResult.callables(callables, false); - } - - if (model.enums.containsKey(identifierExpr.name())) { - return ExprResult.type(TypeView.typeRef(identifierExpr.name())); - } - - return ExprResult.type(TypeView.unknown()); - } - if (expression instanceof PbsAst.GroupExpr groupExpr) { - return analyzeExpression( - groupExpr.expression(), - scope, - expectedType, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - use, - valueContext); - } - if (expression instanceof PbsAst.TupleExpr tupleExpr) { - final var fields = new ArrayList(tupleExpr.items().size()); - for (final var item : tupleExpr.items()) { - final var value = analyzeExpression( - item.expression(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - fields.add(new TupleField(item.label(), value)); - } - return ExprResult.type(TypeView.tuple(fields)); - } - if (expression instanceof PbsAst.BlockExpr blockExpr) { - return ExprResult.type(analyzeBlock( - blockExpr.block(), - scope, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - valueContext)); - } - if (expression instanceof PbsAst.UnaryExpr unaryExpr) { - final var operand = analyzeExpression( - unaryExpr.expression(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - if ("!".equals(unaryExpr.operator()) || "not".equals(unaryExpr.operator())) { - return ExprResult.type(TypeView.bool()); - } - if ("-".equals(unaryExpr.operator()) && (isInt(operand) || isFloat(operand))) { - return ExprResult.type(operand); - } - return ExprResult.type(TypeView.unknown()); - } - if (expression instanceof PbsAst.BinaryExpr binaryExpr) { - final var left = analyzeExpression( - binaryExpr.left(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - final var right = analyzeExpression( - binaryExpr.right(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - return ExprResult.type(inferBinaryResult(binaryExpr.operator(), left, right)); - } - if (expression instanceof PbsAst.MemberExpr memberExpr) { - return analyzeMemberExpression( - memberExpr, - scope, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - use); - } - if (expression instanceof PbsAst.CallExpr callExpr) { - return analyzeCallExpression( - callExpr, - scope, - expectedType, - returnType, - resultErrorName, - receiverType, - model, - diagnostics); - } - if (expression instanceof PbsAst.ApplyExpr applyExpr) { - return analyzeApplyExpression( - applyExpr, - scope, - expectedType, - returnType, - resultErrorName, - receiverType, - model, - diagnostics); - } - if (expression instanceof PbsAst.IfExpr ifExpr) { - final var condition = analyzeExpression( - ifExpr.condition(), - scope, - TypeView.bool(), - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - if (!isBool(condition)) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_IF_NON_BOOL_CONDITION.name(), - "If expression condition must have bool type", - ifExpr.condition().span()); - } - - final var thenType = analyzeBlock(ifExpr.thenBlock(), scope, returnType, resultErrorName, receiverType, model, diagnostics, true); - final var elseType = analyzeExpression( - ifExpr.elseExpression(), - scope, - expectedType, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - - if (!compatible(thenType, elseType)) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_IF_BRANCH_TYPE_MISMATCH.name(), - "If expression branches must have compatible types", - ifExpr.span()); - return ExprResult.type(TypeView.unknown()); - } - return ExprResult.type(thenType); - } - if (expression instanceof PbsAst.SwitchExpr switchExpr) { - return ExprResult.type(analyzeSwitchExpression( - switchExpr, - scope, - expectedType, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - valueContext)); - } - if (expression instanceof PbsAst.ElseExpr elseExpr) { - final var optional = analyzeExpression( - elseExpr.optionalExpression(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - if (optional.kind() != Kind.OPTIONAL) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_ELSE_NON_OPTIONAL_LEFT.name(), - "Left operand of 'else' must have optional type", - elseExpr.optionalExpression().span()); - analyzeExpression( - elseExpr.fallbackExpression(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true); - return ExprResult.type(TypeView.unknown()); - } - - final var payload = optional.inner(); - final var fallback = analyzeExpression( - elseExpr.fallbackExpression(), - scope, - payload, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - if (!compatible(fallback, payload)) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_ELSE_FALLBACK_TYPE_MISMATCH.name(), - "Fallback expression in 'else' must match optional payload type", - elseExpr.fallbackExpression().span()); - } - return ExprResult.type(payload); - } - if (expression instanceof PbsAst.PropagateExpr propagateExpr) { - final var source = analyzeExpression( - propagateExpr.expression(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - if (source.kind() != Kind.RESULT) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_RESULT_PROPAGATE_NON_RESULT.name(), - "Propagation operator '!' requires result type", - propagateExpr.expression().span()); - return ExprResult.type(TypeView.unknown()); - } - final var sourceError = source.errorType(); - if (resultErrorName == null || sourceError == null || !resultErrorName.equals(sourceError.name())) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_RESULT_PROPAGATE_ERROR_MISMATCH.name(), - "Propagation operator '!' requires matching result error type in enclosing callable", - propagateExpr.span()); - } - return ExprResult.type(source.inner()); - } - if (expression instanceof PbsAst.HandleExpr handleExpr) { - return ExprResult.type(analyzeHandleExpression( - handleExpr, - scope, - returnType, - resultErrorName, - receiverType, - model, - diagnostics)); - } - if (expression instanceof PbsAst.NoneExpr noneExpr) { - if (expectedType == null || expectedType.kind() != Kind.OPTIONAL) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_NONE_WITHOUT_EXPECTED_OPTIONAL.name(), - "'none' requires an expected optional type context", - noneExpr.span()); - return ExprResult.type(TypeView.unknown()); - } - return ExprResult.type(expectedType); - } - if (expression instanceof PbsAst.SomeExpr someExpr) { - final var payloadExpected = expectedType != null && expectedType.kind() == Kind.OPTIONAL - ? expectedType.inner() - : null; - final var payload = analyzeExpression( - someExpr.value(), - scope, - payloadExpected, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - return ExprResult.type(TypeView.optional(payloadExpected == null ? payload : payloadExpected)); - } - if (expression instanceof PbsAst.BindExpr bindExpr) { - final var contextType = analyzeExpression( - bindExpr.contextExpression(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - if (expectedType == null || expectedType.kind() != Kind.CALLBACK) { - return ExprResult.type(TypeView.unknown()); - } - - final var topLevel = model.topLevelCallables.get(bindExpr.functionName()); - if (topLevel == null || topLevel.isEmpty()) { - return ExprResult.type(expectedType); - } - final var compatible = new ArrayList(); - for (final var candidate : topLevel) { - if (candidate.inputTypes().size() != expectedType.callbackInputs().size() + 1) { - continue; - } - if (!compatible(contextType, candidate.inputTypes().getFirst())) { - continue; - } - var ok = true; - for (int i = 0; i < expectedType.callbackInputs().size(); i++) { - if (!compatible(expectedType.callbackInputs().get(i), candidate.inputTypes().get(i + 1))) { - ok = false; - break; - } - } - if (!ok) { - continue; - } - if (!compatible(candidate.outputType(), expectedType.callbackOutput())) { - continue; - } - compatible.add(candidate); - } - if (compatible.size() > 1) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_APPLY_AMBIGUOUS_OVERLOAD.name(), - "Bind target resolves ambiguously for callback type", - bindExpr.span()); - } else if (compatible.isEmpty()) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_APPLY_UNRESOLVED_OVERLOAD.name(), - "Bind target does not resolve for callback type", - bindExpr.span()); - } - return ExprResult.type(expectedType); - } - if (expression instanceof PbsAst.NewExpr newExpr) { - if (model.structs.containsKey(newExpr.typeName())) { - return ExprResult.type(TypeView.struct(newExpr.typeName())); - } - return ExprResult.type(TypeView.unknown()); - } - if (expression instanceof PbsAst.AsExpr asExpr) { - analyzeExpression( - asExpr.expression(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true); - if (model.contracts.containsKey(asExpr.contractName())) { - return ExprResult.type(TypeView.contract(asExpr.contractName())); - } - return ExprResult.type(TypeView.unknown()); - } - - if (expression instanceof PbsAst.OkExpr okExpr) { - analyzeExpression( - okExpr.value(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true); - return ExprResult.type(TypeView.unknown()); - } - if (expression instanceof PbsAst.ErrExpr errExpr) { - return ExprResult.type(TypeView.unknown()); - } - - return ExprResult.type(TypeView.unknown()); - } - - private ExprResult analyzeCallExpression( - final PbsAst.CallExpr callExpr, - final Scope scope, - final TypeView expectedType, - final TypeView returnType, - final String resultErrorName, - final TypeView receiverType, - final Model model, - final DiagnosticSink diagnostics) { - final var callee = analyzeExpression( - callExpr.callee(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.CALL_TARGET, - true); - - final TypeView argumentType; - if (callExpr.arguments().isEmpty()) { - argumentType = TypeView.unit(); - } else if (callExpr.arguments().size() == 1) { - argumentType = analyzeExpression( - callExpr.arguments().getFirst(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - } else { - final var fields = new ArrayList(callExpr.arguments().size()); - for (final var argument : callExpr.arguments()) { - final var itemType = analyzeExpression( - argument, - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - fields.add(new TupleField(null, itemType)); - } - argumentType = TypeView.tuple(fields); - } - - return resolveCallableApplication( - callExpr.span(), - callExpr.callee().span(), - callee, - argumentType, - expectedType, - diagnostics); - } - - private ExprResult analyzeApplyExpression( - final PbsAst.ApplyExpr applyExpr, - final Scope scope, - final TypeView expectedType, - final TypeView returnType, - final String resultErrorName, - final TypeView receiverType, - final Model model, - final DiagnosticSink diagnostics) { - final var callee = analyzeExpression( - applyExpr.callee(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.APPLY_TARGET, - true); - final var argument = analyzeExpression( - applyExpr.argument(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - - return resolveCallableApplication( - applyExpr.span(), - applyExpr.callee().span(), - callee, - argument, - expectedType, - diagnostics); - } - - private ExprResult resolveCallableApplication( - final Span wholeSpan, - final Span calleeSpan, - final ExprResult callee, - final TypeView argumentType, - final TypeView expectedType, - final DiagnosticSink diagnostics) { - final var candidates = callee.callables(); - if (candidates.isEmpty()) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_APPLY_NON_CALLABLE_TARGET.name(), - "Apply/call target is not callable", - calleeSpan); - return ExprResult.type(TypeView.unknown()); - } - - final var compatible = new ArrayList(); - for (final var candidate : candidates) { - if (inputCompatible(candidate.inputTypes(), argumentType)) { - compatible.add(candidate); - } - } - - if (compatible.isEmpty()) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_APPLY_UNRESOLVED_OVERLOAD.name(), - "No callable overload matches the provided argument", - wholeSpan); - return ExprResult.type(TypeView.unknown()); - } - - if (compatible.size() > 1 && expectedType != null) { - final var narrowed = compatible.stream() - .filter(candidate -> compatible(candidate.outputType(), expectedType)) - .toList(); - if (narrowed.size() == 1) { - return ExprResult.type(narrowed.getFirst().outputType()); - } - if (!narrowed.isEmpty()) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_APPLY_AMBIGUOUS_OVERLOAD.name(), - "Callable overload resolution remains ambiguous", - wholeSpan); - return ExprResult.type(TypeView.unknown()); - } - } - - if (compatible.size() > 1) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_APPLY_AMBIGUOUS_OVERLOAD.name(), - "Callable overload resolution is ambiguous", - wholeSpan); - return ExprResult.type(TypeView.unknown()); - } - - return ExprResult.type(compatible.getFirst().outputType()); - } - - private boolean inputCompatible(final List inputTypes, final TypeView argumentType) { - if (inputTypes.isEmpty()) { - return isUnit(argumentType) || argumentType.kind() == Kind.UNKNOWN; - } - if (inputTypes.size() == 1) { - return compatible(argumentType, inputTypes.getFirst()); - } - if (argumentType.kind() != Kind.TUPLE) { - return false; - } - if (argumentType.tupleFields().size() != inputTypes.size()) { - return false; - } - for (int i = 0; i < inputTypes.size(); i++) { - if (!compatible(argumentType.tupleFields().get(i).type(), inputTypes.get(i))) { - return false; - } - } - return true; - } - - private ExprResult analyzeMemberExpression( - final PbsAst.MemberExpr memberExpr, - final Scope scope, - final TypeView returnType, - final String resultErrorName, - final TypeView receiverType, - final Model model, - final DiagnosticSink diagnostics, - final ExprUse use) { - final var receiver = analyzeExpression( - memberExpr.receiver(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - - if (receiver.kind() == Kind.TYPE_REF) { - final var enumCases = model.enums.get(receiver.name()); - if (enumCases != null && enumCases.contains(memberExpr.memberName())) { - return ExprResult.type(TypeView.enumType(receiver.name())); - } - diagnostics.error( - PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), - "Invalid type member access", - memberExpr.span()); - return ExprResult.type(TypeView.unknown()); - } - - if (receiver.kind() == Kind.TUPLE) { - if (receiver.tupleFields().size() <= 1) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), - "Member projection is not defined for single-slot carrier values", - memberExpr.span()); - return ExprResult.type(TypeView.unknown()); - } - for (final var field : receiver.tupleFields()) { - if (memberExpr.memberName().equals(field.label())) { - return ExprResult.type(field.type()); - } - } - diagnostics.error( - PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), - "Tuple label '%s' does not exist".formatted(memberExpr.memberName()), - memberExpr.span()); - return ExprResult.type(TypeView.unknown()); - } - - if (receiver.kind() == Kind.STRUCT) { - final var struct = model.structs.get(receiver.name()); - if (struct == null) { - return ExprResult.type(TypeView.unknown()); - } - final var fieldType = struct.fields().get(memberExpr.memberName()); - if (fieldType != null) { - return ExprResult.type(fieldType); - } - final var methods = struct.methods().get(memberExpr.memberName()); - if (methods != null && !methods.isEmpty()) { - if (use == ExprUse.VALUE) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_BARE_METHOD_EXTRACTION.name(), - "Bare method extraction is not allowed in PBS core", - memberExpr.span()); - return ExprResult.type(TypeView.unknown()); - } - return ExprResult.callables(methods, true); - } - diagnostics.error( - PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), - "Struct member '%s' does not exist".formatted(memberExpr.memberName()), - memberExpr.span()); - return ExprResult.type(TypeView.unknown()); - } - - if (receiver.kind() == Kind.SERVICE) { - final var service = model.services.get(receiver.name()); - if (service == null) { - return ExprResult.type(TypeView.unknown()); - } - final var methods = service.methods().get(memberExpr.memberName()); - if (methods != null && !methods.isEmpty()) { - if (use == ExprUse.VALUE) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_BARE_METHOD_EXTRACTION.name(), - "Bare method extraction is not allowed in PBS core", - memberExpr.span()); - return ExprResult.type(TypeView.unknown()); - } - return ExprResult.callables(methods, true); - } - diagnostics.error( - PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), - "Service member '%s' does not exist".formatted(memberExpr.memberName()), - memberExpr.span()); - return ExprResult.type(TypeView.unknown()); - } - - if (receiver.kind() == Kind.CONTRACT) { - final var contract = model.contracts.get(receiver.name()); - if (contract == null) { - return ExprResult.type(TypeView.unknown()); - } - final var methods = contract.methods().get(memberExpr.memberName()); - if (methods != null && !methods.isEmpty()) { - if (use == ExprUse.VALUE) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_BARE_METHOD_EXTRACTION.name(), - "Bare method extraction is not allowed in PBS core", - memberExpr.span()); - return ExprResult.type(TypeView.unknown()); - } - return ExprResult.callables(methods, true); - } - diagnostics.error( - PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), - "Contract member '%s' does not exist".formatted(memberExpr.memberName()), - memberExpr.span()); - return ExprResult.type(TypeView.unknown()); - } - - diagnostics.error( - PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), - "Invalid member access target", - memberExpr.span()); - return ExprResult.type(TypeView.unknown()); - } - - private TypeView analyzeSwitchExpression( - final PbsAst.SwitchExpr switchExpr, - final Scope scope, - final TypeView expectedType, - final TypeView returnType, - final String resultErrorName, - final TypeView receiverType, - final Model model, - final DiagnosticSink diagnostics, - final boolean valueContext) { - final var selector = analyzeExpression( - switchExpr.selector(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - - final var selectorComparable = isScalarComparable(selector) || selector.kind() == Kind.ENUM; - if (!selectorComparable) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_SWITCH_SELECTOR_INVALID.name(), - "Switch selector type is not supported", - switchExpr.selector().span()); - } - - final var seenPatterns = new HashSet(); - boolean hasWildcard = false; - TypeView armType = null; - - for (final var arm : switchExpr.arms()) { - final var patternKey = switchPatternKey(arm.pattern()); - if (arm.pattern() instanceof PbsAst.WildcardSwitchPattern) { - hasWildcard = true; - } else if (!seenPatterns.add(patternKey)) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_SWITCH_DUPLICATE_PATTERN.name(), - "Duplicate switch pattern", - arm.pattern().span()); - } - - final var patternType = switchPatternType(arm.pattern(), model, diagnostics); - if (patternType != null && !compatible(patternType, selector)) { - diagnostics.error( - 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(), - scope, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - true); - if (armType == null) { - armType = currentArmType; - } else if (!compatible(currentArmType, armType)) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_SWITCH_ARM_TYPE_MISMATCH.name(), - "Switch arm block types must be compatible", - arm.span()); - } - } - - if (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( - PbsSemanticsErrors.E_SEM_SWITCH_NON_EXHAUSTIVE.name(), - "Switch expression in value position must be exhaustive", - switchExpr.span()); - } - } - - if (armType == null) { - return TypeView.unit(); - } - if (expectedType != null && compatible(armType, expectedType)) { - return 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)) { - diagnostics.error( - 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 Scope scope, - final TypeView returnType, - final String resultErrorName, - final TypeView receiverType, - final Model model, - final DiagnosticSink diagnostics) { - final var sourceType = analyzeExpression( - handleExpr.value(), - scope, - null, - returnType, - resultErrorName, - receiverType, - model, - diagnostics, - ExprUse.VALUE, - true).type(); - if (sourceType.kind() != Kind.RESULT) { - diagnostics.error( - PbsSemanticsErrors.E_SEM_HANDLE_NON_RESULT.name(), - "Handle requires result expression", - handleExpr.value().span()); - return TypeView.unknown(); - } - if (resultErrorName == null) { - diagnostics.error( - 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 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( - 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( - 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( - PbsSemanticsErrors.E_SEM_HANDLE_ERROR_MISMATCH.name(), - "Handle remap target must match enclosing callable result error type", - arm.remapTarget().span()); - } - continue; - } - analyzeBlock(arm.block(), scope, returnType, resultErrorName, receiverType, model, diagnostics, true); - } - - if (!hasWildcard && !sourceCases.isEmpty() && !matchedCases.containsAll(sourceCases)) { - diagnostics.error( - 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 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 TypeView inferBinaryResult(final String operator, final TypeView left, final TypeView right) { - if ("+".equals(operator) || "-".equals(operator) || "*".equals(operator) || "/".equals(operator) || "%".equals(operator)) { - if (isFloat(left) || isFloat(right)) { - return TypeView.floatType(); - } - if (isInt(left) && isInt(right)) { - return TypeView.intType(); - } - return TypeView.unknown(); - } - if ("==".equals(operator) - || "!=".equals(operator) - || "<".equals(operator) - || "<=".equals(operator) - || ">".equals(operator) - || ">=".equals(operator) - || "and".equals(operator) - || "or".equals(operator) - || "&&".equals(operator) - || "||".equals(operator)) { - return TypeView.bool(); - } - return TypeView.unknown(); - } - - private boolean compatible(final TypeView actual, final TypeView expected) { - if (actual == null || expected == null) { - return true; - } - if (actual.kind() == Kind.UNKNOWN || expected.kind() == Kind.UNKNOWN) { - return true; - } - if (actual.kind() != expected.kind()) { - return false; - } - return switch (actual.kind()) { - case UNIT, INT, FLOAT, BOOL, STR -> true; - case STRUCT, SERVICE, CONTRACT, CALLBACK, ENUM, ERROR, TYPE_REF -> actual.name().equals(expected.name()); - case OPTIONAL -> compatible(actual.inner(), expected.inner()); - case RESULT -> compatible(actual.errorType(), expected.errorType()) && compatible(actual.inner(), expected.inner()); - case TUPLE -> tupleCompatible(actual, expected); - case UNKNOWN -> true; - }; - } - - private boolean tupleCompatible(final TypeView actual, final TypeView expected) { - if (actual.tupleFields().size() != expected.tupleFields().size()) { - return false; - } - for (int i = 0; i < actual.tupleFields().size(); i++) { - if (!compatible(actual.tupleFields().get(i).type(), expected.tupleFields().get(i).type())) { - return false; - } - } - return true; - } - - private TypeView callableReturnType( - final PbsAst.ReturnKind returnKind, - final PbsAst.TypeRef returnType, - final PbsAst.TypeRef resultErrorType, - final Model model) { - return switch (returnKind) { - case INFERRED_UNIT, EXPLICIT_UNIT -> TypeView.unit(); - case PLAIN -> collapseReturnPayload(typeFromTypeRef(returnType, model, null)); - case RESULT -> TypeView.result( - typeFromTypeRef(resultErrorType, model, null), - collapseReturnPayload(typeFromTypeRef(returnType, model, null))); - }; - } - - private TypeView collapseReturnPayload(final TypeView type) { - if (type == null) { - return TypeView.unit(); - } - if (type.kind() == Kind.TUPLE && type.tupleFields().size() == 1) { - return type.tupleFields().getFirst().type(); - } - return type; - } - - private TypeView typeFromTypeRef( - final PbsAst.TypeRef typeRef, - final Model model, - final TypeView receiverType) { - if (typeRef == null) { - return TypeView.unit(); - } - return switch (typeRef.kind()) { - case UNIT -> TypeView.unit(); - case SELF -> receiverType == null ? TypeView.unknown() : receiverType; - case SIMPLE -> simpleType(typeRef.name(), model); - case OPTIONAL -> TypeView.optional(typeFromTypeRef(typeRef.inner(), model, receiverType)); - case GROUP -> typeFromTypeRef(typeRef.inner(), model, receiverType); - case NAMED_TUPLE -> { - final var fields = new ArrayList(typeRef.fields().size()); - for (final var field : typeRef.fields()) { - fields.add(new TupleField(field.label(), typeFromTypeRef(field.typeRef(), model, receiverType))); - } - yield TypeView.tuple(fields); - } - case ERROR -> TypeView.unknown(); - }; - } - - private TypeView simpleType(final String name, final Model model) { - if ("int".equals(name)) { - return TypeView.intType(); - } - if ("float".equals(name)) { - return TypeView.floatType(); - } - if ("bool".equals(name)) { - return TypeView.bool(); - } - if ("str".equals(name)) { - return TypeView.str(); - } - if (model.structs.containsKey(name)) { - return TypeView.struct(name); - } - if (model.services.containsKey(name)) { - return TypeView.service(name); - } - if (model.contracts.containsKey(name)) { - return TypeView.contract(name); - } - if (model.enums.containsKey(name)) { - return TypeView.enumType(name); - } - if (model.errors.containsKey(name)) { - return TypeView.error(name); - } - final var callbackSignature = model.callbacks.get(name); - if (callbackSignature != null) { - return TypeView.callback(name, callbackSignature.inputTypes(), callbackSignature.outputType()); - } - return TypeView.unknown(); - } - - private boolean isScalarComparable(final TypeView type) { - return isBool(type) || isInt(type) || isFloat(type) || isStr(type); - } - - private boolean isUnit(final TypeView type) { - return type.kind() == Kind.UNIT || type.kind() == Kind.UNKNOWN; - } - - private boolean isBool(final TypeView type) { - return type.kind() == Kind.BOOL || type.kind() == Kind.UNKNOWN; - } - - private boolean isInt(final TypeView type) { - return type.kind() == Kind.INT || type.kind() == Kind.UNKNOWN; - } - - private boolean isFloat(final TypeView type) { - return type.kind() == Kind.FLOAT || type.kind() == Kind.UNKNOWN; - } - - private boolean isStr(final TypeView type) { - return type.kind() == Kind.STR || type.kind() == Kind.UNKNOWN; - } - - private record ExprResult( - TypeView type, - List callables, - boolean methodTarget) { - private static ExprResult type(final TypeView type) { - return new ExprResult(type, List.of(), false); - } - - private static ExprResult callables(final List callables, final boolean methodTarget) { - return new ExprResult(TypeView.unknown(), List.copyOf(callables), methodTarget); - } - } - - private enum ExprUse { - VALUE, - CALL_TARGET, - APPLY_TARGET - } - - private enum Kind { - UNKNOWN, - UNIT, - INT, - FLOAT, - BOOL, - STR, - STRUCT, - SERVICE, - CONTRACT, - CALLBACK, - ENUM, - ERROR, - OPTIONAL, - RESULT, - TUPLE, - TYPE_REF - } - - private record TupleField( - String label, - TypeView type) { - } - - private record TypeView( - Kind kind, - String name, - List tupleFields, - TypeView inner, - TypeView errorType, - List callbackInputs, - TypeView callbackOutput) { - private static TypeView unknown() { - return new TypeView(Kind.UNKNOWN, null, List.of(), null, null, List.of(), null); - } - - private static TypeView unit() { - return new TypeView(Kind.UNIT, "unit", List.of(), null, null, List.of(), null); - } - - private static TypeView intType() { - return new TypeView(Kind.INT, "int", List.of(), null, null, List.of(), null); - } - - private static TypeView floatType() { - return new TypeView(Kind.FLOAT, "float", List.of(), null, null, List.of(), null); - } - - private static TypeView bool() { - return new TypeView(Kind.BOOL, "bool", List.of(), null, null, List.of(), null); - } - - private static TypeView str() { - return new TypeView(Kind.STR, "str", List.of(), null, null, List.of(), null); - } - - private static TypeView struct(final String name) { - return new TypeView(Kind.STRUCT, name, List.of(), null, null, List.of(), null); - } - - private static TypeView service(final String name) { - return new TypeView(Kind.SERVICE, name, List.of(), null, null, List.of(), null); - } - - private static TypeView contract(final String name) { - return new TypeView(Kind.CONTRACT, name, List.of(), null, null, List.of(), null); - } - - private static TypeView callback( - final String name, - final List inputTypes, - final TypeView outputType) { - return new TypeView(Kind.CALLBACK, name, List.of(), null, null, List.copyOf(inputTypes), outputType); - } - - private static TypeView enumType(final String name) { - return new TypeView(Kind.ENUM, name, List.of(), null, null, List.of(), null); - } - - private static TypeView error(final String name) { - return new TypeView(Kind.ERROR, name, List.of(), null, null, List.of(), null); - } - - private static TypeView optional(final TypeView inner) { - return new TypeView(Kind.OPTIONAL, "optional", List.of(), inner, null, List.of(), null); - } - - private static TypeView result(final TypeView error, final TypeView payload) { - return new TypeView(Kind.RESULT, "result", List.of(), payload, error, List.of(), null); - } - - private static TypeView tuple(final List fields) { - return new TypeView(Kind.TUPLE, "tuple", List.copyOf(fields), null, null, List.of(), null); - } - - private static TypeView typeRef(final String name) { - return new TypeView(Kind.TYPE_REF, name, List.of(), null, null, List.of(), null); - } - } - - private record CallableSymbol( - String name, - List inputTypes, - TypeView outputType, - Span span) { - } - - private record CallbackSignature( - List inputTypes, - TypeView outputType) { - } - - private record StructInfo( - Map fields, - Map> methods) { - } - - private record ServiceInfo( - Map> methods) { - } - - private record ContractInfo( - Map> methods) { - } - - private static final class Model { - private final Map> topLevelCallables = new HashMap<>(); - private final Map structs = new HashMap<>(); - private final Map services = new HashMap<>(); - private final Map contracts = new HashMap<>(); - private final Map callbacks = new HashMap<>(); - private final Map> enums = new HashMap<>(); - private final Map> errors = new HashMap<>(); - private final Map constTypes = new HashMap<>(); - private final Map serviceSingletons = new HashMap<>(); - - private static Model from(final PbsAst.File ast) { - final var model = new Model(); - for (final var topDecl : ast.topDecls()) { - if (topDecl instanceof PbsAst.StructDecl structDecl) { - final var fields = new HashMap(); - for (final var field : structDecl.fields()) { - fields.put(field.name(), model.typeFrom(field.typeRef())); - } - final var methods = new HashMap>(); - for (final var method : structDecl.methods()) { - methods.computeIfAbsent(method.name(), ignored -> new ArrayList<>()) - .add(model.callableFrom( - method.name(), - method.parameters(), - method.returnKind(), - method.returnType(), - method.resultErrorType(), - method.span())); - } - model.structs.put(structDecl.name(), new StructInfo(fields, methods)); - continue; - } - if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) { - final var methods = new HashMap>(); - for (final var method : serviceDecl.methods()) { - methods.computeIfAbsent(method.name(), ignored -> new ArrayList<>()) - .add(model.callableFrom( - method.name(), - method.parameters(), - method.returnKind(), - method.returnType(), - method.resultErrorType(), - method.span())); - } - model.services.put(serviceDecl.name(), new ServiceInfo(methods)); - model.serviceSingletons.put(serviceDecl.name(), TypeView.service(serviceDecl.name())); - continue; - } - if (topDecl instanceof PbsAst.ContractDecl contractDecl) { - final var methods = new HashMap>(); - for (final var signature : contractDecl.signatures()) { - methods.computeIfAbsent(signature.name(), ignored -> new ArrayList<>()) - .add(model.callableFrom( - signature.name(), - signature.parameters(), - signature.returnKind(), - signature.returnType(), - signature.resultErrorType(), - signature.span())); - } - model.contracts.put(contractDecl.name(), new ContractInfo(methods)); - continue; - } - if (topDecl instanceof PbsAst.FunctionDecl functionDecl) { - model.topLevelCallables.computeIfAbsent(functionDecl.name(), ignored -> new ArrayList<>()) - .add(model.callableFrom( - functionDecl.name(), - functionDecl.parameters(), - functionDecl.returnKind(), - functionDecl.returnType(), - functionDecl.resultErrorType(), - functionDecl.span())); - continue; - } - if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) { - final var symbol = model.callableFrom( - callbackDecl.name(), - callbackDecl.parameters(), - callbackDecl.returnKind(), - callbackDecl.returnType(), - callbackDecl.resultErrorType(), - callbackDecl.span()); - model.callbacks.put(callbackDecl.name(), new CallbackSignature(symbol.inputTypes(), symbol.outputType())); - continue; - } - if (topDecl instanceof PbsAst.EnumDecl enumDecl) { - final var cases = new HashSet(); - for (final var enumCase : enumDecl.cases()) { - cases.add(enumCase.name()); - } - model.enums.put(enumDecl.name(), cases); - continue; - } - if (topDecl instanceof PbsAst.ErrorDecl errorDecl) { - model.errors.put(errorDecl.name(), new HashSet<>(errorDecl.cases().asList())); - continue; - } - if (topDecl instanceof PbsAst.ConstDecl constDecl && constDecl.explicitType() != null) { - model.constTypes.put(constDecl.name(), model.typeFrom(constDecl.explicitType())); - } - } - return model; - } - - private CallableSymbol callableFrom( - final String name, - final ReadOnlyList parameters, - final PbsAst.ReturnKind returnKind, - final PbsAst.TypeRef returnType, - final PbsAst.TypeRef resultErrorType, - final Span span) { - final var input = new ArrayList(parameters.size()); - for (final var parameter : parameters) { - input.add(typeFrom(parameter.typeRef())); - } - return new CallableSymbol(name, input, callableReturn(returnKind, returnType, resultErrorType), span); - } - - private TypeView callableReturn( - final PbsAst.ReturnKind returnKind, - final PbsAst.TypeRef returnType, - final PbsAst.TypeRef resultErrorType) { - return switch (returnKind) { - case INFERRED_UNIT, EXPLICIT_UNIT -> TypeView.unit(); - case PLAIN -> collapse(typeFrom(returnType)); - case RESULT -> TypeView.result(typeFrom(resultErrorType), collapse(typeFrom(returnType))); - }; - } - - private TypeView collapse(final TypeView type) { - if (type.kind() == Kind.TUPLE && type.tupleFields().size() == 1) { - return type.tupleFields().getFirst().type(); - } - return type; - } - - private TypeView typeFrom(final PbsAst.TypeRef typeRef) { - if (typeRef == null) { - return TypeView.unit(); - } - return switch (typeRef.kind()) { - case UNIT -> TypeView.unit(); - case SELF -> TypeView.unknown(); - case SIMPLE -> { - if ("int".equals(typeRef.name())) { - yield TypeView.intType(); - } - if ("float".equals(typeRef.name())) { - yield TypeView.floatType(); - } - if ("bool".equals(typeRef.name())) { - yield TypeView.bool(); - } - if ("str".equals(typeRef.name())) { - yield TypeView.str(); - } - if (structs.containsKey(typeRef.name())) { - yield TypeView.struct(typeRef.name()); - } - if (services.containsKey(typeRef.name())) { - yield TypeView.service(typeRef.name()); - } - if (contracts.containsKey(typeRef.name())) { - yield TypeView.contract(typeRef.name()); - } - if (enums.containsKey(typeRef.name())) { - yield TypeView.enumType(typeRef.name()); - } - if (errors.containsKey(typeRef.name())) { - yield TypeView.error(typeRef.name()); - } - final var callbackSignature = callbacks.get(typeRef.name()); - if (callbackSignature != null) { - yield TypeView.callback(typeRef.name(), callbackSignature.inputTypes(), callbackSignature.outputType()); - } - yield TypeView.unknown(); - } - case OPTIONAL -> TypeView.optional(typeFrom(typeRef.inner())); - case GROUP -> typeFrom(typeRef.inner()); - case NAMED_TUPLE -> { - final var fields = new ArrayList(typeRef.fields().size()); - for (final var field : typeRef.fields()) { - fields.add(new TupleField(field.label(), typeFrom(field.typeRef()))); - } - yield TypeView.tuple(fields); - } - case ERROR -> TypeView.unknown(); - }; - } - } - - private static final class Scope { - private final Map names = new HashMap<>(); - - private Scope copy() { - final var scope = new Scope(); - scope.names.putAll(names); - return scope; - } - - private void bind(final String name, final TypeView type) { - names.put(name, type); - } - - private TypeView resolve(final String name) { - return names.get(name); - } + flowBodyAnalyzer.validate(ast, diagnostics); } } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowTypeOps.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowTypeOps.java new file mode 100644 index 00000000..23e8b4a2 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/semantics/PbsFlowTypeOps.java @@ -0,0 +1,197 @@ +package p.studio.compiler.pbs.semantics; + +import p.studio.compiler.pbs.ast.PbsAst; +import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Kind; +import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Model; +import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.TupleField; +import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.TypeView; + +import java.util.ArrayList; +import java.util.List; + +final class PbsFlowTypeOps { + TypeView inferBinaryResult(final String operator, final TypeView left, final TypeView right) { + if ("+".equals(operator) || "-".equals(operator) || "*".equals(operator) || "/".equals(operator) || "%".equals(operator)) { + if (isFloat(left) || isFloat(right)) { + return TypeView.floatType(); + } + if (isInt(left) && isInt(right)) { + return TypeView.intType(); + } + return TypeView.unknown(); + } + if ("==".equals(operator) + || "!=".equals(operator) + || "<".equals(operator) + || "<=".equals(operator) + || ">".equals(operator) + || ">=".equals(operator) + || "and".equals(operator) + || "or".equals(operator) + || "&&".equals(operator) + || "||".equals(operator)) { + return TypeView.bool(); + } + return TypeView.unknown(); + } + + boolean compatible(final TypeView actual, final TypeView expected) { + if (actual == null || expected == null) { + return true; + } + if (actual.kind() == Kind.UNKNOWN || expected.kind() == Kind.UNKNOWN) { + return true; + } + if (actual.kind() != expected.kind()) { + return false; + } + return switch (actual.kind()) { + case UNIT, INT, FLOAT, BOOL, STR -> true; + case STRUCT, SERVICE, CONTRACT, CALLBACK, ENUM, ERROR, TYPE_REF -> actual.name().equals(expected.name()); + case OPTIONAL -> compatible(actual.inner(), expected.inner()); + case RESULT -> compatible(actual.errorType(), expected.errorType()) && compatible(actual.inner(), expected.inner()); + case TUPLE -> tupleCompatible(actual, expected); + case UNKNOWN -> true; + }; + } + + boolean inputCompatible(final List inputTypes, final TypeView argumentType) { + if (inputTypes.isEmpty()) { + return isUnit(argumentType) || argumentType.kind() == Kind.UNKNOWN; + } + if (inputTypes.size() == 1) { + return compatible(argumentType, inputTypes.getFirst()); + } + if (argumentType.kind() != Kind.TUPLE) { + return false; + } + if (argumentType.tupleFields().size() != inputTypes.size()) { + return false; + } + for (int i = 0; i < inputTypes.size(); i++) { + if (!compatible(argumentType.tupleFields().get(i).type(), inputTypes.get(i))) { + return false; + } + } + return true; + } + + TypeView callableReturnType( + final PbsAst.ReturnKind returnKind, + final PbsAst.TypeRef returnType, + final PbsAst.TypeRef resultErrorType, + final Model model) { + return switch (returnKind) { + case INFERRED_UNIT, EXPLICIT_UNIT -> TypeView.unit(); + case PLAIN -> collapseReturnPayload(typeFromTypeRef(returnType, model, null)); + case RESULT -> TypeView.result( + typeFromTypeRef(resultErrorType, model, null), + collapseReturnPayload(typeFromTypeRef(returnType, model, null))); + }; + } + + TypeView typeFromTypeRef( + final PbsAst.TypeRef typeRef, + final Model model, + final TypeView receiverType) { + if (typeRef == null) { + return TypeView.unit(); + } + return switch (typeRef.kind()) { + case UNIT -> TypeView.unit(); + case SELF -> receiverType == null ? TypeView.unknown() : receiverType; + case SIMPLE -> simpleType(typeRef.name(), model); + case OPTIONAL -> TypeView.optional(typeFromTypeRef(typeRef.inner(), model, receiverType)); + case GROUP -> typeFromTypeRef(typeRef.inner(), model, receiverType); + case NAMED_TUPLE -> { + final var fields = new ArrayList(typeRef.fields().size()); + for (final var field : typeRef.fields()) { + fields.add(new TupleField(field.label(), typeFromTypeRef(field.typeRef(), model, receiverType))); + } + yield TypeView.tuple(fields); + } + case ERROR -> TypeView.unknown(); + }; + } + + boolean isScalarComparable(final TypeView type) { + return isBool(type) || isInt(type) || isFloat(type) || isStr(type); + } + + boolean isUnit(final TypeView type) { + return type.kind() == Kind.UNIT || type.kind() == Kind.UNKNOWN; + } + + boolean isBool(final TypeView type) { + return type.kind() == Kind.BOOL || type.kind() == Kind.UNKNOWN; + } + + boolean isInt(final TypeView type) { + return type.kind() == Kind.INT || type.kind() == Kind.UNKNOWN; + } + + boolean isFloat(final TypeView type) { + return type.kind() == Kind.FLOAT || type.kind() == Kind.UNKNOWN; + } + + boolean isStr(final TypeView type) { + return type.kind() == Kind.STR || type.kind() == Kind.UNKNOWN; + } + + private boolean tupleCompatible(final TypeView actual, final TypeView expected) { + if (actual.tupleFields().size() != expected.tupleFields().size()) { + return false; + } + for (int i = 0; i < actual.tupleFields().size(); i++) { + if (!compatible(actual.tupleFields().get(i).type(), expected.tupleFields().get(i).type())) { + return false; + } + } + return true; + } + + private TypeView collapseReturnPayload(final TypeView type) { + if (type == null) { + return TypeView.unit(); + } + if (type.kind() == Kind.TUPLE && type.tupleFields().size() == 1) { + return type.tupleFields().getFirst().type(); + } + return type; + } + + private TypeView simpleType(final String name, final Model model) { + if ("int".equals(name)) { + return TypeView.intType(); + } + if ("float".equals(name)) { + return TypeView.floatType(); + } + if ("bool".equals(name)) { + return TypeView.bool(); + } + if ("str".equals(name)) { + return TypeView.str(); + } + if (model.structs.containsKey(name)) { + return TypeView.struct(name); + } + if (model.services.containsKey(name)) { + return TypeView.service(name); + } + if (model.contracts.containsKey(name)) { + return TypeView.contract(name); + } + if (model.enums.containsKey(name)) { + return TypeView.enumType(name); + } + if (model.errors.containsKey(name)) { + return TypeView.error(name); + } + final var callbackSignature = model.callbacks.get(name); + if (callbackSignature != null) { + return TypeView.callback(name, callbackSignature.inputTypes(), callbackSignature.outputType()); + } + return TypeView.unknown(); + } +}