Skip to content

GROOVY-12166 backport: STC: scope statement-level instanceof narrowing to the … - #2797

Merged
paulk-asert merged 1 commit into
apache:GROOVY_5_1_Xfrom
paulk-asert:groovy12166_51X
Aug 18, 2026
Merged

GROOVY-12166 backport: STC: scope statement-level instanceof narrowing to the …#2797
paulk-asert merged 1 commit into
apache:GROOVY_5_1_Xfrom
paulk-asert:groovy12166_51X

Conversation

@paulk-asert

Copy link
Copy Markdown
Contributor

…member being visited

Temporary type information from instanceof is keyed by the target variable — for a field access, the shared FieldNode. if/loops/ternaries and expression statements bracket their own frames, but a narrowing recorded at statement level (an explicit return field instanceof Sub, or an assert field instanceof Sub, whose propagation to subsequent statements is intentional) lands in the enclosing frame, which was the frame pushed for the whole class visit. Keyed by the shared FieldNode, it then applied to every member visited afterwards: the checker stamped the narrowed type on unrelated field reads and the static compiler emitted a spurious checkcast, throwing ClassCastException at runtime whenever the field held a different subtype.

Members now push their own frame (methods and constructors, property and field initializers, object initializer blocks), so statement-level narrowing still flows within a member but can never survive into another; narrowing has no meaning past the member boundary. Intra-member flow typing (assert, if-branch) is unchanged.

The checker-side leak predates Groovy 5 but was masked by codegen: StaticTypesTypeChooser resolved types from the declared target first until GROOVY-11375 (5.0.0-alpha-9) gave the expression-stamped inferred type priority, exposing the stale narrowing as a checkcast. That change is correct; the fix belongs here.

…member being visited

Temporary type information from instanceof is keyed by the target
variable — for a field access, the shared FieldNode. if/loops/ternaries
and expression statements bracket their own frames, but a narrowing
recorded at statement level (an explicit `return field instanceof Sub`,
or an `assert field instanceof Sub`, whose propagation to subsequent
statements is intentional) lands in the enclosing frame, which was the
frame pushed for the whole class visit. Keyed by the shared FieldNode,
it then applied to every member visited afterwards: the checker stamped
the narrowed type on unrelated field reads and the static compiler
emitted a spurious checkcast, throwing ClassCastException at runtime
whenever the field held a different subtype.

Members now push their own frame (methods and constructors, property
and field initializers, object initializer blocks), so statement-level
narrowing still flows within a member but can never survive into
another; narrowing has no meaning past the member boundary. Intra-member
flow typing (assert, if-branch) is unchanged.

The checker-side leak predates Groovy 5 but was masked by codegen:
StaticTypesTypeChooser resolved types from the declared target first
until GROOVY-11375 (5.0.0-alpha-9) gave the expression-stamped inferred
type priority, exposing the stale narrowing as a checkcast. That change
is correct; the fix belongs here.
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.2789%. Comparing base (514e66f) to head (8218af1).

Additional details and impacted files

Impacted file tree graph

@@                  Coverage Diff                   @@
##             GROOVY_5_1_X      #2797        +/-   ##
======================================================
+ Coverage         67.2715%   67.2789%   +0.0074%     
  Complexity          29631      29631                
======================================================
  Files                1383       1383                
  Lines              117109     117117         +8     
  Branches            20596      20596                
======================================================
+ Hits                78781      78795        +14     
+ Misses              31777      31774         -3     
+ Partials             6551       6548         -3     
Files with missing lines Coverage Δ
...roovy/transform/stc/StaticTypeCheckingVisitor.java 87.4009% <100.0000%> (+0.0276%) ⬆️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@paulk-asert paulk-asert changed the title GROOVY-12166: STC: scope statement-level instanceof narrowing to the … GROOVY-12166 backport: STC: scope statement-level instanceof narrowing to the … Aug 17, 2026
@paulk-asert
paulk-asert merged commit 8d39f5a into apache:GROOVY_5_1_X Aug 18, 2026
29 checks passed
@paulk-asert
paulk-asert deleted the groovy12166_51X branch August 18, 2026 00:53
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.

2 participants