refactoring and reducing complexity

This commit is contained in:
bQUARKz 2026-03-10 09:14:44 +00:00
parent 1c38b19772
commit 14dce7ecc4
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
7 changed files with 422 additions and 56 deletions

View File

@ -175,3 +175,23 @@ Foco: reduzir complexidade estrutural de `PbsExprParser` sem alterar precedence,
3. `PR-11.3-pbs-expr-parser-postfix-primary-and-construction-decomposition.md` 3. `PR-11.3-pbs-expr-parser-postfix-primary-and-construction-decomposition.md`
4. `PR-11.4-pbs-expr-parser-literals-patterns-and-shared-token-utilities.md` 4. `PR-11.4-pbs-expr-parser-literals-patterns-and-shared-token-utilities.md`
5. `PR-11.5-pbs-expr-parser-regression-hardening-and-final-cleanup.md` 5. `PR-11.5-pbs-expr-parser-regression-hardening-and-final-cleanup.md`
### Onda O12 - Flow Expression Analyzer Maintainability Refactor (No Functional Change)
Foco: reduzir complexidade estrutural de `PbsFlowExpressionAnalyzer` sem alterar inferencia, diagnosticos, matching de tipos ou comportamento observavel da analise semantica.
1. `PR-12.1-flow-expression-analyzer-context-and-dispatch-foundation.md`
2. `PR-12.2-flow-expression-analyzer-literal-and-structural-expression-splitting.md`
3. `PR-12.3-flow-expression-analyzer-call-and-member-resolution-decomposition.md`
4. `PR-12.4-flow-expression-analyzer-switch-and-handle-decomposition.md`
5. `PR-12.5-flow-expression-analyzer-regression-hardening-and-final-cleanup.md`
### Onda O13 - Flow Body Analyzer Maintainability Refactor (No Functional Change)
Foco: reduzir complexidade estrutural de `PbsFlowBodyAnalyzer` sem alterar regras de flow, completion, assignment checking ou diagnosticos observaveis.
1. `PR-13.1-flow-body-analyzer-context-and-entrypoint-foundation.md`
2. `PR-13.2-flow-body-analyzer-callable-body-and-completion-decomposition.md`
3. `PR-13.3-flow-body-analyzer-statement-analysis-splitting.md`
4. `PR-13.4-flow-body-analyzer-assignment-target-resolution-decomposition.md`
5. `PR-13.5-flow-body-analyzer-regression-hardening-and-final-cleanup.md`

View File

@ -0,0 +1,69 @@
# PR-12.1 - Flow Expression Analyzer Context and Dispatch Foundation
## Briefing
`PbsFlowExpressionAnalyzer` concentra estado compartilhado e um dispatch grande de `Expression -> semantic analysis` em uma unica classe.
Esta PR introduz a base estrutural para decomposicao sem alterar funcionalidade: contexto compartilhado, helpers comuns e uma fronteira clara entre fachada e implementacoes especializadas.
## Motivation
### Dor atual que esta PR resolve
1. A classe combina orquestracao, recursion, block delegation e regras semanticas detalhadas.
2. Extracoes futuras ficam arriscadas sem um contexto compartilhado explicito.
3. O maior hotspot da classe e o dispatch principal de `analyzeExpressionInternal`, que hoje depende de muitos parametros repetidos.
## Target
Estabelecer uma fundacao segura para o refactor:
1. contexto compartilhado para analise de expressao,
2. encapsulamento de argumentos recorrentes,
3. fachada pequena em `PbsFlowExpressionAnalyzer`.
## Dependencies
Nenhuma.
## Scope
1. Introduzir um contexto/request object para `scope`, `expectedType`, `returnType`, `resultErrorName`, `receiverType`, `model`, `diagnostics`, `use` e `valueContext`.
2. Encapsular a dependencia de `BlockAnalyzer` nessa camada.
3. Reorganizar o dispatch principal sem mover ainda regras complexas para outras classes.
## Non-Goals
1. Nao alterar inferencia de tipos.
2. Nao alterar diagnosticos ou spans.
3. Nao reescrever `PbsFlowTypeOps` ou `PbsFlowBodyAnalyzer`.
## Method
### O que deve ser feito explicitamente
1. Criar um contexto pequeno e explicito para a analise recursiva.
2. Manter `PbsFlowExpressionAnalyzer` como entrypoint package-private.
3. Preservar a ordem atual de recursion e emissao de diagnosticos.
4. Garantir que esta PR seja puramente estrutural.
## Acceptance Criteria
1. `PbsFlowExpressionAnalyzer` continua com o mesmo contrato observavel.
2. A quantidade de parametros repetidos no dispatch recursivo e reduzida.
3. Nao ha mudanca funcional em inferencia, erros ou comportamento da suite.
## Tests
1. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test --tests *PbsSemanticsDeclarationsTest*`
2. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test --tests *PbsSemanticsAssignmentTest*`
3. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test --tests *PbsSemanticsApplyResolutionTest*`
4. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test`
## Affected Documents
1. Nenhum documento normativo.
## Open Questions
1. O contexto deve ser imutavel por chamada recursiva ou permitir derivacao incremental. A recomendacao e usar derivacao imutavel leve para reduzir risco de regressao.

View File

@ -0,0 +1,71 @@
# PR-12.2 - Flow Expression Analyzer Literal and Structural Expression Splitting
## Briefing
Grande parte do dispatch de `PbsFlowExpressionAnalyzer` cobre formas literais e estruturais de expressao: literais, `this`, identificadores, grupos, tuplas, blocos, unarios, binarios, `none/some`, `new`, `as`, `ok/err`.
Esta PR extrai essas familias para componentes menores, sem alterar funcionalidade.
## Motivation
### Dor atual que esta PR resolve
1. O dispatch principal mistura casos triviais com regras mais complexas de call/member/switch/handle.
2. Formas estruturais simples podem ser separadas com risco baixo e alta reducao de complexidade.
3. Sem esse corte, o dispatch central continua inchado mesmo com contexto compartilhado.
## Target
Separar a analise de expressoes literais e estruturais:
1. literais e identificadores,
2. grupos, tuplas e blocos,
3. unario/binario,
4. `none/some`, `new`, `bind`-independent simple forms, `as`, `ok/err`.
## Dependencies
Prerequisito direto:
1. `PR-12.1`
## Scope
1. Extrair casos simples de `analyzeExpressionInternal` para helpers/colaboradores dedicados.
2. Manter o resultado em `ExprResult` exatamente como hoje.
3. Preservar todas as chamadas a `typeOps` e `blockAnalyzer`.
## Non-Goals
1. Nao alterar regras de callback/bind.
2. Nao alterar member access ou overload resolution.
3. Nao alterar switch/handle/result propagation.
## Method
### O que deve ser feito explicitamente
1. Mover primeiro os casos com menor acoplamento semantico.
2. Manter o dispatch principal apenas como roteador.
3. Preservar a ordem atual de recursion para evitar mudanca em diagnosticos acumulados.
## Acceptance Criteria
1. Casos literais e estruturais deixam de ficar todos dentro do dispatch principal.
2. AST/semantics observados pela suite permanecem equivalentes.
3. O hotspot principal da classe reduz tamanho e branching sem alterar comportamento.
## Tests
1. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test --tests *PbsExprParserTest*`
2. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test --tests *PbsSemanticsOptionalResultTest*`
3. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test --tests *PbsSemanticsDeclarationsTest*`
4. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test`
## Affected Documents
1. Nenhum documento normativo.
## Open Questions
1. `BindExpr` deve entrar aqui ou junto de call resolution. A recomendacao e mantê-lo para `PR-12.3`, porque ele depende de matching de callback/callable.

View File

@ -0,0 +1,72 @@
# PR-12.3 - Flow Expression Analyzer Call and Member Resolution Decomposition
## Briefing
`analyzeCallExpression`, `analyzeApplyExpression`, `resolveCallableApplication`, `analyzeMemberExpression` e parte de `BindExpr` concentram a logica de resolucao mais sensivel do analisador.
Esta PR extrai essa superficie para colaboradores menores, sem alterar regras de overload, bare method extraction, acesso a campos ou matching de callback.
## Motivation
### Dor atual que esta PR resolve
1. Member access e callable resolution sao os clusters mais densos depois do dispatch principal.
2. A classe mistura regras de acesso a tuple/struct/service/contract com overload resolution e bind matching.
3. Qualquer ajuste nessas regras hoje exige tocar um arquivo muito central e com alta chance de regressao.
## Target
Separar a analise de resolucao:
1. member access,
2. call/apply overload resolution,
3. bind-to-callback matching,
4. field access permission checks.
## Dependencies
Prerequisitos diretos:
1. `PR-12.1`
2. `PR-12.2`
## Scope
1. Extrair `analyzeCallExpression`, `analyzeApplyExpression`, `resolveCallableApplication`.
2. Extrair `analyzeMemberExpression` e `canReadStructField`.
3. Mover a parte especifica de `BindExpr` para o mesmo cluster de resolucao.
## Non-Goals
1. Nao alterar diagnosticos de overload ambiguous/unresolved.
2. Nao alterar regras de leitura de campo privado/publico.
3. Nao alterar a proibicao de bare method extraction.
## Method
### O que deve ser feito explicitamente
1. Criar um resolver focado em call/member semantics.
2. Preservar o mesmo uso de `TypeView`, `CallableSymbol` e `ExprResult`.
3. Garantir que o order of checks permaneça equivalente, especialmente em erros e narrowing por `expectedType`.
## Acceptance Criteria
1. Call/member resolution deixa de ficar concentrada em `PbsFlowExpressionAnalyzer`.
2. Bare method extraction, field access e overload matching permanecem equivalentes.
3. Suite semantica continua verde sem mudancas funcionais.
## Tests
1. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test --tests *PbsSemanticsApplyResolutionTest*`
2. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test --tests *PbsSemanticsAssignmentTest*`
3. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test --tests *PbsFrontendCompilerTest*`
4. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test`
## Affected Documents
1. Nenhum documento normativo.
## Open Questions
1. `BindExpr` deve compartilhar exatamente o mesmo narrowing path de `call/apply` ou ficar em helper proprio. A recomendacao e helper proprio dentro do mesmo resolver, para evitar acoplamento artificial.

View File

@ -0,0 +1,73 @@
# PR-12.4 - Flow Expression Analyzer Switch and Handle Decomposition
## Briefing
`switch` e `handle` sao as maiores superfices especializadas restantes em `PbsFlowExpressionAnalyzer`, com regras de exaustividade, pattern typing, matching de erro e arm typing.
Esta PR separa essas construcoes em analisadores dedicados, mantendo exatamente o comportamento atual.
## Motivation
### Dor atual que esta PR resolve
1. `analyzeSwitchExpression` e `analyzeHandleExpression` concentram regras de alto branching e recovery semantico.
2. Essas regras sao semanticamente coesas e merecem fronteiras proprias.
3. Sem esse corte, a classe principal continua com os hotspots mais caros de manutencao.
## Target
Extrair a analise especializada de:
1. `switch expression`,
2. `switch pattern typing/keying`,
3. `handle expression`,
4. `handle block arm` e matching de error targets.
## Dependencies
Prerequisitos diretos:
1. `PR-12.1`
2. `PR-12.2`
3. `PR-12.3`
## Scope
1. Extrair `analyzeSwitchExpression`, `switchPatternKey`, `switchPatternType`.
2. Extrair `analyzeHandleExpression`, `analyzeHandleBlockArm`, `matchesTargetError`, `unwrapGroup`.
3. Preservar a interacao com `blockAnalyzer`, `typeOps` e `model`.
## Non-Goals
1. Nao alterar regras de exaustividade de switch em value position.
2. Nao alterar regras de wildcard/default duplication.
3. Nao alterar regras de `handle` para `ok(...)` / `err(...)` terminais.
## Method
### O que deve ser feito explicitamente
1. Criar analisadores especializados por construcao.
2. Preservar ordem de validacoes e diagnosticos.
3. Reusar o contexto compartilhado para evitar nova explosao de parametros.
## Acceptance Criteria
1. `switch` e `handle` deixam de ficar implementados diretamente na classe principal.
2. Exaustividade, arm typing e error matching permanecem equivalentes.
3. Nenhum teste de result flow, control flow ou optional/result muda comportamento esperado.
## Tests
1. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test --tests *PbsSemanticsResultFlowRulesTest*`
2. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test --tests *PbsSemanticsOptionalResultTest*`
3. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test --tests *PbsSemanticsControlFlowTest*`
4. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test`
## Affected Documents
1. Nenhum documento normativo.
## Open Questions
1. `switchPatternKey` e `switchPatternType` devem ficar juntos no mesmo colaborador. A recomendacao e sim, porque ambos definem a semantica da superficie de pattern.

View File

@ -0,0 +1,77 @@
# PR-12.5 - Flow Expression Analyzer Regression Hardening and Final Cleanup
## Briefing
As PRs anteriores reduzem a complexidade estrutural de `PbsFlowExpressionAnalyzer`. Esta PR fecha a trilha com consolidacao final, limpeza de duplicacao residual e endurecimento de regressao, sem alterar funcionalidade.
## Motivation
### Dor atual que esta PR resolve
1. Refactors estruturais em analise semantica podem preservar casos principais e ainda mudar detalhes finos de diagnostico.
2. Ao final da decomposicao, tendem a restar helpers redundantes ou caminhos duplicados.
3. O fechamento da trilha precisa garantir equivalencia observavel e uma fachada final pequena.
## Target
Concluir a trilha de refactor:
1. limpeza estrutural final,
2. consolidacao dos helpers remanescentes,
3. cobertura de regressao para inferencia e diagnosticos.
## Dependencies
Prerequisitos diretos:
1. `PR-12.1`
2. `PR-12.2`
3. `PR-12.3`
4. `PR-12.4`
## Scope
1. Remover duplicacao residual apos as extracoes.
2. Garantir que `PbsFlowExpressionAnalyzer` fique reduzido a composicao/orquestracao.
3. Reforcar testes de regressao para call/member resolution, result flow, switch e handle.
## Non-Goals
1. Nao alterar taxonomia de `PbsSemanticsErrors`.
2. Nao alterar `TypeView`, `ExprResult` ou `PbsFlowTypeOps`.
3. Nao introduzir novas regras semanticas.
## Method
### O que deve ser feito explicitamente
1. Fazer uma passada final de consolidacao.
2. Verificar equivalencia com foco em:
- inferencia de tipos,
- overload resolution,
- access checks,
- switch/handle exhaustiveness e matching,
- diagnosticos emitidos.
3. Encerrar a trilha com uma fachada pequena e clara.
## Acceptance Criteria
1. `PbsFlowExpressionAnalyzer` termina como orquestrador, nao como concentrador de toda a logica.
2. Nao ha mudanca funcional observavel em analise, tipos inferidos ou diagnosticos.
3. Suite do frontend PBS passa integralmente.
## Tests
1. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test --tests *PbsSemanticsApplyResolutionTest*`
2. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test --tests *PbsSemanticsResultFlowRulesTest*`
3. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test --tests *PbsSemanticsOptionalResultTest*`
4. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test --tests *PbsSemanticsControlFlowTest*`
5. `:prometeu-compiler:frontends:prometeu-frontend-pbs:test`
## Affected Documents
1. Nenhum documento normativo.
## Open Questions
1. Nenhuma. A partir desta PR, qualquer mudanca adicional deve ser tratada como evolucao funcional separada.

View File

@ -1,7 +1,9 @@
package p.studio.compiler.pbs.semantics; package p.studio.compiler.pbs.semantics;
import p.studio.compiler.pbs.ast.PbsAst; import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.source.Span;
import p.studio.compiler.source.diagnostics.DiagnosticSink; import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.diagnostics.Diagnostics;
import p.studio.utilities.structures.ReadOnlyList; import p.studio.utilities.structures.ReadOnlyList;
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.ExprUse; import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.ExprUse;
import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Kind; import p.studio.compiler.pbs.semantics.PbsFlowSemanticSupport.Kind;
@ -20,10 +22,6 @@ final class PbsFlowBodyAnalyzer {
boolean assignable) { boolean assignable) {
} }
public void validate(final PbsAst.File ast, final DiagnosticSink diagnostics) {
validate(ast, ReadOnlyList.empty(), diagnostics);
}
public void validate( public void validate(
final PbsAst.File ast, final PbsAst.File ast,
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls, final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
@ -118,14 +116,14 @@ final class PbsFlowBodyAnalyzer {
} }
if (enforceResultCompletion) { if (enforceResultCompletion) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_POSSIBLE_FALLTHROUGH_RESULT.name(), PbsSemanticsErrors.E_SEM_POSSIBLE_FALLTHROUGH_RESULT.name(),
"Possible fallthrough in result callable body; all paths must return explicitly", "Possible fallthrough in result callable body; all paths must return explicitly",
body.span()); body.span());
return; return;
} }
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_POSSIBLE_FALLTHROUGH_NON_UNIT.name(), PbsSemanticsErrors.E_SEM_POSSIBLE_FALLTHROUGH_NON_UNIT.name(),
"Possible fallthrough in plain non-unit callable body; all paths must return explicitly", "Possible fallthrough in plain non-unit callable body; all paths must return explicitly",
body.span()); body.span());
@ -171,26 +169,15 @@ final class PbsFlowBodyAnalyzer {
private boolean expressionAlwaysReturns( private boolean expressionAlwaysReturns(
final PbsAst.Expression expression, final PbsAst.Expression expression,
final Model model) { final Model model) {
if (expression == null) { return switch (expression) {
return false; case PbsAst.GroupExpr groupExpr -> expressionAlwaysReturns(groupExpr.expression(), model);
} case PbsAst.BlockExpr blockExpr -> blockAlwaysReturns(blockExpr.block(), model);
if (expression instanceof PbsAst.GroupExpr groupExpr) { case PbsAst.IfExpr ifExpr -> blockAlwaysReturns(ifExpr.thenBlock(), model)
return expressionAlwaysReturns(groupExpr.expression(), model);
}
if (expression instanceof PbsAst.BlockExpr blockExpr) {
return blockAlwaysReturns(blockExpr.block(), model);
}
if (expression instanceof PbsAst.IfExpr ifExpr) {
return blockAlwaysReturns(ifExpr.thenBlock(), model)
&& expressionAlwaysReturns(ifExpr.elseExpression(), model); && expressionAlwaysReturns(ifExpr.elseExpression(), model);
} case PbsAst.SwitchExpr switchExpr -> switchAlwaysReturns(switchExpr, model);
if (expression instanceof PbsAst.SwitchExpr switchExpr) { case PbsAst.HandleExpr handleExpr -> handleAlwaysReturns(handleExpr, model);
return switchAlwaysReturns(switchExpr, model); case null, default -> false;
} };
if (expression instanceof PbsAst.HandleExpr handleExpr) {
return handleAlwaysReturns(handleExpr, model);
}
return false;
} }
private boolean handleAlwaysReturns( private boolean handleAlwaysReturns(
@ -373,7 +360,7 @@ final class PbsFlowBodyAnalyzer {
true, true,
this::analyzeBlock).type(); this::analyzeBlock).type();
if (!typeOps.isBool(condition)) { if (!typeOps.isBool(condition)) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_IF_NON_BOOL_CONDITION.name(), PbsSemanticsErrors.E_SEM_IF_NON_BOOL_CONDITION.name(),
"If statement condition must have bool type", "If statement condition must have bool type",
ifStatement.condition().span()); ifStatement.condition().span());
@ -390,7 +377,7 @@ final class PbsFlowBodyAnalyzer {
if (statement instanceof PbsAst.ForStatement( if (statement instanceof PbsAst.ForStatement(
String iteratorName, PbsAst.TypeRef type, PbsAst.Expression fromExpression, String iteratorName, PbsAst.TypeRef type, PbsAst.Expression fromExpression,
PbsAst.Expression untilExpression, PbsAst.Expression stepExpression, PbsAst.Block body, PbsAst.Expression untilExpression, PbsAst.Expression stepExpression, PbsAst.Block body,
p.studio.compiler.source.Span span Span span
)) { )) {
final var iteratorType = typeOps.typeFromTypeRef(type, model, receiverType); final var iteratorType = typeOps.typeFromTypeRef(type, model, receiverType);
final var fromType = expressionAnalyzer.analyzeExpression( final var fromType = expressionAnalyzer.analyzeExpression(
@ -418,7 +405,7 @@ final class PbsFlowBodyAnalyzer {
true, true,
this::analyzeBlock).type(); this::analyzeBlock).type();
if (!typeOps.compatible(fromType, iteratorType) || !typeOps.compatible(untilType, iteratorType)) { if (!typeOps.compatible(fromType, iteratorType) || !typeOps.compatible(untilType, iteratorType)) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_FOR_TYPE_MISMATCH.name(), PbsSemanticsErrors.E_SEM_FOR_TYPE_MISMATCH.name(),
"For-loop bounds must match declared iterator type", "For-loop bounds must match declared iterator type",
span); span);
@ -437,7 +424,7 @@ final class PbsFlowBodyAnalyzer {
true, true,
this::analyzeBlock).type(); this::analyzeBlock).type();
if (!typeOps.compatible(stepType, iteratorType)) { if (!typeOps.compatible(stepType, iteratorType)) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_FOR_TYPE_MISMATCH.name(), PbsSemanticsErrors.E_SEM_FOR_TYPE_MISMATCH.name(),
"For-loop step must match declared iterator type", "For-loop step must match declared iterator type",
stepExpression.span()); stepExpression.span());
@ -462,7 +449,7 @@ final class PbsFlowBodyAnalyzer {
true, true,
this::analyzeBlock).type(); this::analyzeBlock).type();
if (!typeOps.isBool(condition)) { if (!typeOps.isBool(condition)) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_WHILE_NON_BOOL_CONDITION.name(), PbsSemanticsErrors.E_SEM_WHILE_NON_BOOL_CONDITION.name(),
"While condition must have bool type", "While condition must have bool type",
whileStatement.condition().span()); whileStatement.condition().span());
@ -529,8 +516,8 @@ final class PbsFlowBodyAnalyzer {
final TypeView receiverType, final TypeView receiverType,
final Model model, final Model model,
final DiagnosticSink diagnostics) { final DiagnosticSink diagnostics) {
if (returnType.kind() != PbsFlowSemanticSupport.Kind.RESULT || resultErrorName == null) { if (returnType.kind() != Kind.RESULT || resultErrorName == null) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_RESULT_FLOW_INVALID_POSITION.name(), PbsSemanticsErrors.E_SEM_RESULT_FLOW_INVALID_POSITION.name(),
"'ok(...)' is only allowed when returning from a result callable", "'ok(...)' is only allowed when returning from a result callable",
okExpr.span()); okExpr.span());
@ -563,7 +550,7 @@ final class PbsFlowBodyAnalyzer {
true, true,
this::analyzeBlock).type(); this::analyzeBlock).type();
if (!typeOps.compatible(actualType, payloadType)) { if (!typeOps.compatible(actualType, payloadType)) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_RESULT_OK_PAYLOAD_MISMATCH.name(), PbsSemanticsErrors.E_SEM_RESULT_OK_PAYLOAD_MISMATCH.name(),
"Payload in 'ok(...)' is incompatible with result payload type", "Payload in 'ok(...)' is incompatible with result payload type",
okExpr.value().span()); okExpr.value().span());
@ -576,8 +563,8 @@ final class PbsFlowBodyAnalyzer {
final String resultErrorName, final String resultErrorName,
final Model model, final Model model,
final DiagnosticSink diagnostics) { final DiagnosticSink diagnostics) {
if (returnType.kind() != PbsFlowSemanticSupport.Kind.RESULT || resultErrorName == null) { if (returnType.kind() != Kind.RESULT || resultErrorName == null) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_RESULT_FLOW_INVALID_POSITION.name(), PbsSemanticsErrors.E_SEM_RESULT_FLOW_INVALID_POSITION.name(),
"'err(...)' is only allowed when returning from a result callable", "'err(...)' is only allowed when returning from a result callable",
errExpr.span()); errExpr.span());
@ -585,7 +572,7 @@ final class PbsFlowBodyAnalyzer {
} }
if (!matchesTargetError(errExpr.errorPath(), resultErrorName, model)) { if (!matchesTargetError(errExpr.errorPath(), resultErrorName, model)) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_RESULT_ERROR_LABEL_INVALID.name(), PbsSemanticsErrors.E_SEM_RESULT_ERROR_LABEL_INVALID.name(),
"Error label in 'err(...)' does not match enclosing result error type", "Error label in 'err(...)' does not match enclosing result error type",
errExpr.errorPath().span()); errExpr.errorPath().span());
@ -621,7 +608,7 @@ final class PbsFlowBodyAnalyzer {
final Model model, final Model model,
final DiagnosticSink diagnostics) { final DiagnosticSink diagnostics) {
final var target = resolveAssignmentTarget(assignStatement.target(), scope, receiverType, model, diagnostics); final var target = resolveAssignmentTarget(assignStatement.target(), scope, receiverType, model, diagnostics);
final var expectedType = target == null ? null : target.type(); final var expectedType = target.type();
final var valueType = expressionAnalyzer.analyzeExpression( final var valueType = expressionAnalyzer.analyzeExpression(
assignStatement.value(), assignStatement.value(),
scope, scope,
@ -635,12 +622,12 @@ final class PbsFlowBodyAnalyzer {
true, true,
this::analyzeBlock).type(); this::analyzeBlock).type();
if (target == null || !target.assignable()) { if (!target.assignable()) {
return; return;
} }
if (!assignmentCompatible(assignStatement.operator(), target.type(), valueType)) { if (!assignmentCompatible(assignStatement.operator(), target.type(), valueType)) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_ASSIGN_TYPE_MISMATCH.name(), PbsSemanticsErrors.E_SEM_ASSIGN_TYPE_MISMATCH.name(),
"Assigned value type is not compatible with assignment target", "Assigned value type is not compatible with assignment target",
assignStatement.span()); assignStatement.span());
@ -658,12 +645,12 @@ final class PbsFlowBodyAnalyzer {
if (lValue.pathSegments().isEmpty()) { if (lValue.pathSegments().isEmpty()) {
if (rootIsThis) { if (rootIsThis) {
if (receiverType == null) { if (receiverType == null) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_INVALID_THIS_CONTEXT.name(), PbsSemanticsErrors.E_SEM_INVALID_THIS_CONTEXT.name(),
"Invalid 'this' usage outside struct/service methods and constructors", "Invalid 'this' usage outside struct/service methods and constructors",
lValue.span()); lValue.span());
} }
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_ASSIGN_TARGET_NOT_ASSIGNABLE.name(), PbsSemanticsErrors.E_SEM_ASSIGN_TARGET_NOT_ASSIGNABLE.name(),
"Assignment target is not assignable", "Assignment target is not assignable",
lValue.span()); lValue.span());
@ -673,7 +660,7 @@ final class PbsFlowBodyAnalyzer {
final var localType = scope.resolve(rootName); final var localType = scope.resolve(rootName);
if (localType != null) { if (localType != null) {
if (!scope.isMutable(rootName)) { if (!scope.isMutable(rootName)) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_ASSIGN_TARGET_NOT_ASSIGNABLE.name(), PbsSemanticsErrors.E_SEM_ASSIGN_TARGET_NOT_ASSIGNABLE.name(),
"Cannot assign to immutable local '%s'".formatted(rootName), "Cannot assign to immutable local '%s'".formatted(rootName),
lValue.span()); lValue.span());
@ -683,14 +670,14 @@ final class PbsFlowBodyAnalyzer {
} }
if (isKnownNonAssignableRoot(rootName, model)) { if (isKnownNonAssignableRoot(rootName, model)) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_ASSIGN_TARGET_NOT_ASSIGNABLE.name(), PbsSemanticsErrors.E_SEM_ASSIGN_TARGET_NOT_ASSIGNABLE.name(),
"Assignment target is not assignable", "Assignment target is not assignable",
lValue.span()); lValue.span());
return new AssignmentTargetResolution(TypeView.unknown(), false); return new AssignmentTargetResolution(TypeView.unknown(), false);
} }
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_ASSIGN_TARGET_UNRESOLVED.name(), PbsSemanticsErrors.E_SEM_ASSIGN_TARGET_UNRESOLVED.name(),
"Assignment target '%s' does not resolve".formatted(rootName), "Assignment target '%s' does not resolve".formatted(rootName),
lValue.span()); lValue.span());
@ -701,7 +688,7 @@ final class PbsFlowBodyAnalyzer {
var currentReceiverIsThis = rootIsThis; var currentReceiverIsThis = rootIsThis;
if (rootIsThis) { if (rootIsThis) {
if (receiverType == null) { if (receiverType == null) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_INVALID_THIS_CONTEXT.name(), PbsSemanticsErrors.E_SEM_INVALID_THIS_CONTEXT.name(),
"Invalid 'this' usage outside struct/service methods and constructors", "Invalid 'this' usage outside struct/service methods and constructors",
lValue.span()); lValue.span());
@ -717,7 +704,7 @@ final class PbsFlowBodyAnalyzer {
} else if (model.constTypes.containsKey(rootName)) { } else if (model.constTypes.containsKey(rootName)) {
currentType = model.constTypes.get(rootName); currentType = model.constTypes.get(rootName);
} else { } else {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_ASSIGN_TARGET_UNRESOLVED.name(), PbsSemanticsErrors.E_SEM_ASSIGN_TARGET_UNRESOLVED.name(),
"Assignment target '%s' does not resolve".formatted(rootName), "Assignment target '%s' does not resolve".formatted(rootName),
lValue.span()); lValue.span());
@ -728,8 +715,8 @@ final class PbsFlowBodyAnalyzer {
for (int i = 0; i < lValue.pathSegments().size(); i++) { for (int i = 0; i < lValue.pathSegments().size(); i++) {
final var segment = lValue.pathSegments().get(i); final var segment = lValue.pathSegments().get(i);
final var isLastSegment = i == lValue.pathSegments().size() - 1; final var isLastSegment = i == lValue.pathSegments().size() - 1;
if (currentType.kind() != PbsFlowSemanticSupport.Kind.STRUCT) { if (currentType.kind() != Kind.STRUCT) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(),
"Assignment target segment '%s' is not a struct field".formatted(segment), "Assignment target segment '%s' is not a struct field".formatted(segment),
lValue.span()); lValue.span());
@ -742,7 +729,7 @@ final class PbsFlowBodyAnalyzer {
} }
final var field = struct.fields().get(segment); final var field = struct.fields().get(segment);
if (field == null) { if (field == null) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(), PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(),
"Struct member '%s' does not exist".formatted(segment), "Struct member '%s' does not exist".formatted(segment),
lValue.span()); lValue.span());
@ -750,11 +737,11 @@ final class PbsFlowBodyAnalyzer {
} }
final var ownerContext = receiverType != null final var ownerContext = receiverType != null
&& receiverType.kind() == PbsFlowSemanticSupport.Kind.STRUCT && receiverType.kind() == Kind.STRUCT
&& receiverType.name().equals(currentType.name()); && receiverType.name().equals(currentType.name());
if (isLastSegment) { if (isLastSegment) {
if (!canWriteStructField(field, ownerContext, currentReceiverIsThis)) { if (!canWriteStructField(field, ownerContext, currentReceiverIsThis)) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_FIELD_WRITE_ACCESS_DENIED.name(), PbsSemanticsErrors.E_SEM_FIELD_WRITE_ACCESS_DENIED.name(),
"Write access to struct field '%s' is not permitted".formatted(segment), "Write access to struct field '%s' is not permitted".formatted(segment),
lValue.span()); lValue.span());
@ -764,7 +751,7 @@ final class PbsFlowBodyAnalyzer {
} }
if (!canReadStructField(field, ownerContext, currentReceiverIsThis)) { if (!canReadStructField(field, ownerContext, currentReceiverIsThis)) {
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics, Diagnostics.error(diagnostics,
PbsSemanticsErrors.E_SEM_FIELD_READ_ACCESS_DENIED.name(), PbsSemanticsErrors.E_SEM_FIELD_READ_ACCESS_DENIED.name(),
"Read access to struct field '%s' is not permitted".formatted(segment), "Read access to struct field '%s' is not permitted".formatted(segment),
lValue.span()); lValue.span());
@ -792,11 +779,8 @@ final class PbsFlowBodyAnalyzer {
case MUL_ASSIGN -> "*"; case MUL_ASSIGN -> "*";
case DIV_ASSIGN -> "/"; case DIV_ASSIGN -> "/";
case MOD_ASSIGN -> "%"; case MOD_ASSIGN -> "%";
case ASSIGN -> "="; default -> throw new IllegalStateException("Unexpected value: " + operator);
}; };
if ("=".equals(binaryOperator)) {
return typeOps.compatible(valueType, targetType);
}
final var resultType = typeOps.inferBinaryResult(binaryOperator, targetType, valueType); final var resultType = typeOps.inferBinaryResult(binaryOperator, targetType, valueType);
return typeOps.compatible(resultType, targetType); return typeOps.compatible(resultType, targetType);
} }