-
-
Notifications
You must be signed in to change notification settings - Fork 794
refactor(analyze/types): union static-member flattening #8546
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: 58a37de The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
WalkthroughThis change hardens static-member flattening for union types by switching from a pre-collected variant mapping to an iterative per-variant resolution loop. It skips global-undefined variants, pushes unknown on resolution failures, aborts if a variant is a typeof-expression, and preserves unknown variants in resulting unions. Tests were added to ensure unknown variants are preserved and typeof-expression variants are not flattened. Two new public diagnostic types (JsModuleInfoDiagnostic, ModuleDiagnostic) and a test preventing types-limit explosions for a recursive parentElement pattern were also introduced. Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (4 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This patch tightens the behavior for TypeofExpression::StaticMember on unions: - If a union variant is unknown, propagate unknown into the result. - Resolve the member across union variants directly (no distribution into nested inferred expressions). - If a union variant is still an inferred expression type (TypeofExpression), refuse to flatten to avoid re-entering expression-based inference that can lead to runaway growth.
575d3e4 to
cdc136e
Compare
|
At a glance, this looks a lot like #8536, which is already merged. |
|
@dyc3 At a glance, yes, and it's because I started working on it yesterday and finished it up after a sleep (at which point this PR was made). But it has 3 benefits in addition to that PR: it avoids vec allocation, correctly handles unknown whereas 8536 stripped unknowns, and it fixes the recursion whereas 8536 did not. The added tests fail on 8536. |
CodSpeed Performance ReportMerging #8546 will not alter performanceComparing Summary
Footnotes
|
ematipico
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Tests were already added, so one isn't needed anymore
| #[test] | ||
| fn infer_flattened_type_of_static_member_on_union_does_not_flatten_expression_variants() { | ||
| const CODE: &str = r#"interface Node { | ||
| parentNode: Node | null; | ||
| } | ||
| node.parentNode"#; | ||
|
|
||
| let root = parse_ts(CODE); | ||
| let interface_decl = get_interface_declaration(&root); | ||
| let mut resolver = GlobalsResolver::default(); | ||
| let interface_ty = | ||
| TypeData::from_ts_interface_declaration(&mut resolver, ScopeId::GLOBAL, &interface_decl) | ||
| .expect("interface must be inferred"); | ||
| resolver.run_inference(); | ||
|
|
||
| let interface_ref = resolver.reference_to_owned_data(interface_ty); | ||
| let expression_variant = | ||
| resolver.reference_to_owned_data(TypeData::TypeofExpression(Box::new( | ||
| TypeofExpression::StaticMember(TypeofStaticMemberExpression { | ||
| object: TypeReference::unknown(), | ||
| member: Text::new_static("parentNode"), | ||
| }), | ||
| ))); | ||
| let union_ty = TypeData::union_of(&resolver, [interface_ref, expression_variant].into()); | ||
|
|
||
| let expr = get_expression(&root); | ||
| let mut resolver = HardcodedSymbolResolver::new("node", union_ty, resolver); | ||
| let expr_ty = TypeData::from_any_js_expression(&mut resolver, ScopeId::GLOBAL, &expr); | ||
| let expr_ty = expr_ty.inferred(&mut resolver); | ||
|
|
||
| assert!( | ||
| matches!(&expr_ty, TypeData::TypeofExpression(expr) if matches!(expr.as_ref(), TypeofExpression::StaticMember(_))), | ||
| "expected to keep typeof-expression unflattened, got: {expr_ty}", | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test isn't needed anymore because it was already added
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changeset is also not needed, since it was added in the other PR.
This PR was written primarily by GPT 5.2.
Summary
Unknown branch propagation
If node might be unknown, node.parentNode must include unknown.
Without this, inference can become too narrow by effectively “dropping” the unknown branch
Expression-derived union branches
If a union variant is still an inferred expression type (Biome’s internal “type-of-expression”), flattening obj.member can re-enter expression reasoning in a way that’s unstable for patterns like:
So we conservatively refuse to flatten in that case (leave it unflattened for this pass).
Probably fixes #8204.
Verified fix on https://github.com/openstreetmap-ng/openstreetmap-ng/blob/main/app/views/lib/standard-form.ts
Test Plan