Skip to content

feat(callbacks): apply applies_constants at a class top-level body (toplevel:)#42

Merged
felixefelip merged 1 commit into
rbs_inferfrom
feat/erb-toplevel-applies-constants
Jun 14, 2026
Merged

feat(callbacks): apply applies_constants at a class top-level body (toplevel:)#42
felixefelip merged 1 commit into
rbs_inferfrom
feat/erb-toplevel-applies-constants

Conversation

@felixefelip

Copy link
Copy Markdown
Owner

Implementa o lado-Steep de felixefelip/rbs_infer#25 (comentário com a investigação: #25).

Problema

O narrowing de constante via applies_constants (#41) só é aplicado na entrada de métodos (apply_callbacks_for_method). Views ERB, porém, são type-checked como código top-level com # @type self: <ERBClass> anexado ao source (source.rb) — não há método pra ancorar o applies_constants. Resultado: Current.caderneta.* numa view guardada continua acusando nil-risk, mesmo a action renderizadora rodando sob o guard que populou o Current.

Solução: toplevel: no callbacks sidecar

# .steep_callbacks.yml
- class: ERBCadernetaShow
  applies_constants:
    Current: "singleton(Current) & Current::CadernetaPopulated"
  toplevel: true
  • Callbacks::Entry ganha toplevel: true — sinal framework-agnóstico ("aplica no corpo top-level desta classe"; dispensa runs_before). Store#toplevel_entries(class) faz o lookup.
  • Callbacks::ConstantNarrowing (módulo novo) — extrai a lógica "applies_constants map → updates de constant_types" (parse, tolerância a marker pendurado, chaves relativa+absoluta). Reutilizável.
  • TypeCheckService.type_check — pra um arquivo com @type self: C, aplica as entries toplevel de C ao env top-level antes de montar o context (análogo do apply_callbacks_for_method pro corpo do programa).
  • Refactor: apply_callbacks_for_method e marker_references_resolvable? (usado também pelo caminho de postconditions) passam a delegar ao ConstantNarrowing; 3 helpers duplicados removidos do TypeConstruction.

Testes

  • Unit (callbacks_test): Entry parseia toplevel: true sem runs_before; toplevel_entries lookup (+ strip de ::); entry method-style excluída do lookup toplevel. 20/20.
  • E2e (type_check_test): positivo — Current.caderneta.vacinas no corpo top-level de um arquivo @type self: ERBClass narrowa (diagnostics vazios); negativo — entry method-style (sem toplevel) não aplica → mantém nilável → NoMethod. 209/209.
  • with_standard_construction espelha o novo setup top-level (consistente com a duplicação que já fazia do @type self).

Escopo

Lado-Steep apenas. A emissão das entries toplevel: true pras classes ERB (via mapeamento view↔action) é a metade complementar, no rbs_infer.

🤖 Generated with Claude Code

…(toplevel:)

ERB views are type-checked as top-level code with `# @type self: <class>`
(no method), so the method-entry `applies_constants` (steep#41) couldn't
reach them — `Current.*` reads in a guarded view stayed nilable even
though the rendering action ran under the guard
(felixefelip/rbs_infer#25).

- Callbacks::Entry gains `toplevel: true` — a framework-agnostic signal
  "apply this narrowing to the class's top-level body" (no runs_before
  required). Store#toplevel_entries looks them up.
- Callbacks::ConstantNarrowing extracts "applies_constants map →
  constant_types updates" (parse, dangling-marker tolerance, relative+
  absolute TypeName keys), shared by both paths.
- TypeCheckService.type_check applies a `@type self:` class's toplevel
  entries to the top-level env before building the context — the
  analogue of apply_callbacks_for_method for the program body.
- TypeConstruction#apply_callbacks_for_method and
  #marker_references_resolvable? (also used by the postcondition path)
  now delegate to ConstantNarrowing; three duplicated helpers removed.

The emitter side (rbs_infer generating `toplevel: true` ERB entries from
the view↔action mapping) is the complementary half of #25.

callbacks_test 20/20, type_check_test 209/209.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
felixefelip added a commit to felixefelip/rbs_infer that referenced this pull request Jun 14, 2026
…the guard (#25) (#27)

The view-side half of #25. A guarded action's narrowing
(`applies_constants`) reached the controller but not the views it
renders — `Current.caderneta` in a guarded view stayed nilable. ERB
views are checked as top-level code (`# @type self: ERBClass`), so they
need the steep fork's `toplevel: true` callback entries (felixefelip/steep#42).

CurrentAttributesCallbacksGenerator now emits, alongside each guarded
controller's action entry, a `toplevel: true` entry for the convention
view of each guarded action — reusing ViewPathNaming's view↔class
mapping. The view carries the same intersected markers as its action.

Sound for convention views (rendered by that single action). NOT emitted
for partials/layouts (no single rendering action), nor checked against
explicit cross-renders by an unguarded action — the heterogeneous-render
scope the issue put aside. Each ERB entry gets its own applies_constants
hash so the YAML carries no aliases.

Field-checked (with steep#42): app's `caderneta/show.html.erb` and
`sugestoes/index.html.erb` Current.* reads narrow (steep 132 → 130);
the `_doses` partial stays (out of scope). Unit 452, integration 48.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@felixefelip felixefelip merged commit c3150a6 into rbs_infer Jun 14, 2026
8 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant