From 834ee2aa6c052d351aa242ee3b60dc9cebf52625 Mon Sep 17 00:00:00 2001 From: DonIsaac <22823424+DonIsaac@users.noreply.github.com> Date: Wed, 9 Oct 2024 01:34:46 +0000 Subject: [PATCH] fix(semantic): `TSConditionalType` scope enter/exit locations (#6351) Fixes the same problem as #6270, but uses `#[scope(enter_before)]` and `#[scope(exit_after)]` to correct scope entry/exit locations. --- crates/oxc_ast/src/ast/ts.rs | 2 + crates/oxc_ast/src/generated/visit.rs | 4 +- crates/oxc_ast/src/generated/visit_mut.rs | 4 +- .../type-declaration/conditional-nested.snap | 43 +++++++++---------- .../oxc_semantic/tests/integration/scopes.rs | 22 ++++++++++ crates/oxc_traverse/src/generated/walk.rs | 16 +++---- .../snapshots/semantic_typescript.snap | 12 +++--- 7 files changed, 63 insertions(+), 40 deletions(-) diff --git a/crates/oxc_ast/src/ast/ts.rs b/crates/oxc_ast/src/ast/ts.rs index bc2d87054a807..9601204ca0fc0 100644 --- a/crates/oxc_ast/src/ast/ts.rs +++ b/crates/oxc_ast/src/ast/ts.rs @@ -347,10 +347,12 @@ pub struct TSConditionalType<'a> { /// The type before `extends` in the test expression. pub check_type: TSType<'a>, /// The type `check_type` is being tested against. + #[scope(enter_before)] pub extends_type: TSType<'a>, /// The type evaluated to if the test is true. pub true_type: TSType<'a>, /// The type evaluated to if the test is false. + #[scope(exit_before)] pub false_type: TSType<'a>, #[serde(skip)] #[clone_in(default)] diff --git a/crates/oxc_ast/src/generated/visit.rs b/crates/oxc_ast/src/generated/visit.rs index a42a3dacb807a..203a40dea87ec 100644 --- a/crates/oxc_ast/src/generated/visit.rs +++ b/crates/oxc_ast/src/generated/visit.rs @@ -1913,12 +1913,12 @@ pub mod walk { pub fn walk_ts_conditional_type<'a, V: Visit<'a>>(visitor: &mut V, it: &TSConditionalType<'a>) { let kind = AstKind::TSConditionalType(visitor.alloc(it)); visitor.enter_node(kind); - visitor.enter_scope(ScopeFlags::empty(), &it.scope_id); visitor.visit_ts_type(&it.check_type); + visitor.enter_scope(ScopeFlags::empty(), &it.scope_id); visitor.visit_ts_type(&it.extends_type); visitor.visit_ts_type(&it.true_type); - visitor.visit_ts_type(&it.false_type); visitor.leave_scope(); + visitor.visit_ts_type(&it.false_type); visitor.leave_node(kind); } diff --git a/crates/oxc_ast/src/generated/visit_mut.rs b/crates/oxc_ast/src/generated/visit_mut.rs index fa9eda505882c..24042e90ffcd3 100644 --- a/crates/oxc_ast/src/generated/visit_mut.rs +++ b/crates/oxc_ast/src/generated/visit_mut.rs @@ -1956,12 +1956,12 @@ pub mod walk_mut { ) { let kind = AstType::TSConditionalType; visitor.enter_node(kind); - visitor.enter_scope(ScopeFlags::empty(), &it.scope_id); visitor.visit_ts_type(&mut it.check_type); + visitor.enter_scope(ScopeFlags::empty(), &it.scope_id); visitor.visit_ts_type(&mut it.extends_type); visitor.visit_ts_type(&mut it.true_type); - visitor.visit_ts_type(&mut it.false_type); visitor.leave_scope(); + visitor.visit_ts_type(&mut it.false_type); visitor.leave_node(kind); } diff --git a/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/conditional-nested.snap b/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/conditional-nested.snap index 2a7845c2cc41b..c6d68244ae7e9 100644 --- a/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/conditional-nested.snap +++ b/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/conditional-nested.snap @@ -12,45 +12,44 @@ SCOPES { "children": [ { - "children": [ + "children": [], + "flags": "ScopeFlags(StrictMode)", + "id": 2, + "node": "TSConditionalType", + "symbols": [ { - "children": [], - "flags": "ScopeFlags(StrictMode)", - "id": 3, - "node": "TSConditionalType", - "symbols": [ + "flags": "SymbolFlags(TypeParameter)", + "id": 2, + "name": "U", + "node": "TSTypeParameter(U)", + "references": [ { - "flags": "SymbolFlags(TypeParameter)", - "id": 3, + "flags": "ReferenceFlags(Type)", + "id": 2, "name": "U", - "node": "TSTypeParameter(U)", - "references": [ - { - "flags": "ReferenceFlags(Type)", - "id": 5, - "name": "U", - "node_id": 33 - } - ] + "node_id": 19 } ] } - ], + ] + }, + { + "children": [], "flags": "ScopeFlags(StrictMode)", - "id": 2, + "id": 3, "node": "TSConditionalType", "symbols": [ { "flags": "SymbolFlags(TypeParameter)", - "id": 2, + "id": 3, "name": "U", "node": "TSTypeParameter(U)", "references": [ { "flags": "ReferenceFlags(Type)", - "id": 2, + "id": 5, "name": "U", - "node_id": 19 + "node_id": 33 } ] } diff --git a/crates/oxc_semantic/tests/integration/scopes.rs b/crates/oxc_semantic/tests/integration/scopes.rs index f250e89c75025..df68e3c2a47e7 100644 --- a/crates/oxc_semantic/tests/integration/scopes.rs +++ b/crates/oxc_semantic/tests/integration/scopes.rs @@ -239,3 +239,25 @@ fn get_child_ids() { let child_scope_ids = scopes.get_child_ids(child_scope_ids[0]); assert!(child_scope_ids.is_empty()); } + +#[test] +fn test_ts_conditional_types() { + SemanticTester::ts("type A = T extends string ? T : false;") + .has_some_symbol("T") + .has_number_of_references(2) + .test(); + + // Conditional types create a new scope after check_type. + SemanticTester::ts( + "type S = A extends (infer B extends number ? string : never) ? B : false;", + ) + .has_some_symbol("B") + .has_number_of_references(1) + .test(); + + // Inferred type parameter is only available within true branch + SemanticTester::ts("type S = A extends infer R ? never : R") + .has_some_symbol("R") + .has_number_of_references(0) + .test(); +} diff --git a/crates/oxc_traverse/src/generated/walk.rs b/crates/oxc_traverse/src/generated/walk.rs index 61c6a408cca98..0eb8f6b2709f0 100644 --- a/crates/oxc_traverse/src/generated/walk.rs +++ b/crates/oxc_traverse/src/generated/walk.rs @@ -3964,13 +3964,6 @@ pub(crate) unsafe fn walk_ts_conditional_type<'a, Tr: Traverse<'a>>( ctx: &mut TraverseCtx<'a>, ) { traverser.enter_ts_conditional_type(&mut *node, ctx); - let previous_scope_id = ctx.current_scope_id(); - ctx.set_current_scope_id( - (*((node as *mut u8).add(ancestor::OFFSET_TS_CONDITIONAL_TYPE_SCOPE_ID) - as *mut Cell>)) - .get() - .unwrap(), - ); let pop_token = ctx.push_stack(Ancestor::TSConditionalTypeCheckType( ancestor::TSConditionalTypeWithoutCheckType(node, PhantomData), )); @@ -3979,6 +3972,13 @@ pub(crate) unsafe fn walk_ts_conditional_type<'a, Tr: Traverse<'a>>( (node as *mut u8).add(ancestor::OFFSET_TS_CONDITIONAL_TYPE_CHECK_TYPE) as *mut TSType, ctx, ); + let previous_scope_id = ctx.current_scope_id(); + ctx.set_current_scope_id( + (*((node as *mut u8).add(ancestor::OFFSET_TS_CONDITIONAL_TYPE_SCOPE_ID) + as *mut Cell>)) + .get() + .unwrap(), + ); ctx.retag_stack(AncestorType::TSConditionalTypeExtendsType); walk_ts_type( traverser, @@ -3991,6 +3991,7 @@ pub(crate) unsafe fn walk_ts_conditional_type<'a, Tr: Traverse<'a>>( (node as *mut u8).add(ancestor::OFFSET_TS_CONDITIONAL_TYPE_TRUE_TYPE) as *mut TSType, ctx, ); + ctx.set_current_scope_id(previous_scope_id); ctx.retag_stack(AncestorType::TSConditionalTypeFalseType); walk_ts_type( traverser, @@ -3998,7 +3999,6 @@ pub(crate) unsafe fn walk_ts_conditional_type<'a, Tr: Traverse<'a>>( ctx, ); ctx.pop_stack(pop_token); - ctx.set_current_scope_id(previous_scope_id); traverser.exit_ts_conditional_type(&mut *node, ctx); } diff --git a/tasks/coverage/snapshots/semantic_typescript.snap b/tasks/coverage/snapshots/semantic_typescript.snap index 7263bd26e2b7f..190ac592bf944 100644 --- a/tasks/coverage/snapshots/semantic_typescript.snap +++ b/tasks/coverage/snapshots/semantic_typescript.snap @@ -3897,13 +3897,13 @@ Bindings mismatch: after transform: ScopeId(2): ["P", "attrs"] rebuilt : ScopeId(1): ["attrs"] Scope children mismatch: -after transform: ScopeId(2): [ScopeId(3)] +after transform: ScopeId(2): [ScopeId(3), ScopeId(5)] rebuilt : ScopeId(1): [] Bindings mismatch: after transform: ScopeId(6): ["P", "attrs"] rebuilt : ScopeId(2): ["attrs"] Scope children mismatch: -after transform: ScopeId(6): [ScopeId(7)] +after transform: ScopeId(6): [ScopeId(7), ScopeId(8)] rebuilt : ScopeId(2): [] Bindings mismatch: after transform: ScopeId(9): ["P", "attrs"] @@ -8852,7 +8852,7 @@ Bindings mismatch: after transform: ScopeId(3): ["D", "clientDef"] rebuilt : ScopeId(1): ["clientDef"] Scope children mismatch: -after transform: ScopeId(3): [ScopeId(4)] +after transform: ScopeId(3): [ScopeId(4), ScopeId(5)] rebuilt : ScopeId(1): [] Unresolved references mismatch: after transform: ["Record"] @@ -17164,7 +17164,7 @@ Scope children mismatch: after transform: ScopeId(8): [ScopeId(9), ScopeId(10)] rebuilt : ScopeId(2): [] Symbol reference IDs mismatch for "state": -after transform: SymbolId(8): [ReferenceId(11), ReferenceId(12), ReferenceId(13), ReferenceId(15)] +after transform: SymbolId(9): [ReferenceId(11), ReferenceId(12), ReferenceId(13), ReferenceId(15)] rebuilt : SymbolId(1): [ReferenceId(0), ReferenceId(1), ReferenceId(2)] Unresolved references mismatch: after transform: ["State", "true"] @@ -52205,10 +52205,10 @@ Scope children mismatch: after transform: ScopeId(0): [ScopeId(1), ScopeId(4), ScopeId(5), ScopeId(6), ScopeId(7), ScopeId(10), ScopeId(11), ScopeId(12), ScopeId(13), ScopeId(16), ScopeId(17), ScopeId(18), ScopeId(19), ScopeId(22), ScopeId(23), ScopeId(24), ScopeId(25), ScopeId(28), ScopeId(29), ScopeId(30), ScopeId(31), ScopeId(34), ScopeId(35), ScopeId(36), ScopeId(37), ScopeId(40), ScopeId(41), ScopeId(42), ScopeId(43), ScopeId(44), ScopeId(47), ScopeId(48), ScopeId(49), ScopeId(50), ScopeId(51), ScopeId(54), ScopeId(55), ScopeId(56), ScopeId(57), ScopeId(58), ScopeId(61), ScopeId(63), ScopeId(64), ScopeId(67), ScopeId(69), ScopeId(71), ScopeId(73), ScopeId(77), ScopeId(80), ScopeId(84), ScopeId(87), ScopeId(89), ScopeId(90), ScopeId(91), ScopeId(92), ScopeId(95), ScopeId(96), ScopeId(98), ScopeId(99), ScopeId(100), ScopeId(101), ScopeId(102), ScopeId(103), ScopeId(107), ScopeId(108), ScopeId(109), ScopeId(110), ScopeId(111)] rebuilt : ScopeId(0): [ScopeId(1), ScopeId(2)] Reference symbol mismatch for "x1": -after transform: SymbolId(126) "x1" +after transform: SymbolId(127) "x1" rebuilt : Reference symbol mismatch for "x2": -after transform: SymbolId(132) "x2" +after transform: SymbolId(133) "x2" rebuilt : Unresolved references mismatch: after transform: ["Promise"]