From e4ed41d4fa5b96f7e6346bfa2d5a0d73d4d8cfb0 Mon Sep 17 00:00:00 2001 From: Dunqing <29533304+Dunqing@users.noreply.github.com> Date: Thu, 5 Sep 2024 01:50:20 +0000 Subject: [PATCH] refactor(semantic): change the reference flag to `ReferenceFlags::Type` if it is used within a `TSTypeQuery` (#5444) So far, the `ReferenceFlags::TSTypeQuery` only used indicates it is referenced by `TSTypeQuery` that we can confirm the reference should be regarded as a type reference, namely `ReferenceFlags::Type`. This PR adds a `ReferenceFlags::ValueAsType` instead of `ReferenceFlags::TSTypeQuery`. The new flag has the same behavior as the previous one. But it looks more general and is not only used in `TSTypeQuery`. But now it is a temporary flag. We use it to resolve the symbol correctly and replace `ReferenceFlags::ValueAsTyoe` with `ReferenceFlags::Type` after resolved. Also, this change eliminates the inconsistency in behavior between the `Reference::is_type` and `ReferenceFlags::is_type` methods. --- crates/oxc_semantic/src/builder.rs | 31 +++++++-------- crates/oxc_semantic/src/reference.rs | 2 +- .../signatures/property-with-type-import.snap | 2 +- .../class/declaration/type-reference.snap | 2 +- .../import/type-default-value.snap | 2 +- .../import/type-inline-value.snap | 2 +- .../import/type-named-value.snap | 2 +- .../type-arguments2.snap | 2 +- .../type-declaration/function/function2.snap | 2 +- .../type-declaration/index-access3.snap | 2 +- .../signatures/property-computed-name.snap | 2 +- .../type-query-qualified.snap | 2 +- .../type-query-with-parameters.snap | 2 +- .../type-declaration/type-query.snap | 2 +- crates/oxc_syntax/src/reference.rs | 38 +++++++++---------- 15 files changed, 44 insertions(+), 51 deletions(-) diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index 47a9f04954985..b97c8286f6998 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -479,25 +479,22 @@ impl<'a> SemanticBuilder<'a> { let flags = reference.flags(); if flags.is_type() && symbol_flags.can_be_referenced_by_type() || flags.is_value() && symbol_flags.can_be_referenced_by_value() - || flags.is_ts_type_query() && symbol_flags.is_import() + || flags.is_value_as_type() + && (symbol_flags.can_be_referenced_by_value() + || symbol_flags.is_type_import()) { - // The non type-only ExportSpecifier can reference a type/value symbol, - // If the symbol is a value symbol and reference flag is not type-only, remove the type flag. - if symbol_flags.is_value() && !flags.is_type_only() { + if flags.is_value_as_type() { + // Resolve pending type references (e.g., from `typeof` expressions) to proper type references. + *reference.flags_mut() = ReferenceFlags::Type; + } else if symbol_flags.is_value() && !flags.is_type_only() { + // The non type-only ExportSpecifier can reference a type/value symbol, + // If the symbol is a value symbol and reference flag is not type-only, remove the type flag. *reference.flags_mut() -= ReferenceFlags::Type; } else { // If the symbol is a type symbol and reference flag is not type-only, remove the value flag. *reference.flags_mut() -= ReferenceFlags::Value; } - // import type { T } from './mod'; type A = typeof T - // ^ can reference type-only import - // If symbol is type-import, we need to replace the ReferenceFlags::Value with ReferenceFlags::Type - if flags.is_ts_type_query() && symbol_flags.is_type_import() { - *reference.flags_mut() -= ReferenceFlags::Value; - *reference.flags_mut() |= ReferenceFlags::Type; - } - reference.set_symbol_id(symbol_id); resolved_references.push(reference_id); false @@ -1834,19 +1831,18 @@ impl<'a> SemanticBuilder<'a> { if signature.key.is_expression() { // interface A { [prop]: string } // ^^^^^ The property can reference value or [`SymbolFlags::TypeImport`] symbol - self.current_reference_flags = - ReferenceFlags::Read | ReferenceFlags::TSTypeQuery; // TODO: Should use another flag + self.current_reference_flags = ReferenceFlags::ValueAsType; } } AstKind::TSTypeQuery(_) => { // type A = typeof a; // ^^^^^^^^ - self.current_reference_flags = ReferenceFlags::Read | ReferenceFlags::TSTypeQuery; + self.current_reference_flags = ReferenceFlags::ValueAsType; } AstKind::TSTypeParameterInstantiation(_) => { // type A = typeof a; // ^^^ avoid treat T as a value and TSTypeQuery - self.current_reference_flags -= ReferenceFlags::Read | ReferenceFlags::TSTypeQuery; + self.current_reference_flags -= ReferenceFlags::ValueAsType; } AstKind::TSTypeName(_) => { match self.nodes.parent_kind(self.current_node_id) { @@ -1862,7 +1858,8 @@ impl<'a> SemanticBuilder<'a> { // ^^^ Keep the current reference flag } _ => { - if !self.current_reference_flags.is_ts_type_query() { + // Handled in `AstKind::PropertySignature` or `AstKind::TSTypeQuery` + if !self.current_reference_flags.is_value_as_type() { self.current_reference_flags = ReferenceFlags::Type; } } diff --git a/crates/oxc_semantic/src/reference.rs b/crates/oxc_semantic/src/reference.rs index e781639f14d43..19ef7c66f4df7 100644 --- a/crates/oxc_semantic/src/reference.rs +++ b/crates/oxc_semantic/src/reference.rs @@ -117,6 +117,6 @@ impl Reference { /// Returns `true` if this reference is used in a type context. #[inline] pub fn is_type(&self) -> bool { - self.flags.is_type() || self.flags.is_ts_type_query() + self.flags.is_type() } } diff --git a/crates/oxc_semantic/tests/fixtures/oxc/type-declarations/signatures/property-with-type-import.snap b/crates/oxc_semantic/tests/fixtures/oxc/type-declarations/signatures/property-with-type-import.snap index 9b43e0cc37ae2..2c844c0dd35a8 100644 --- a/crates/oxc_semantic/tests/fixtures/oxc/type-declarations/signatures/property-with-type-import.snap +++ b/crates/oxc_semantic/tests/fixtures/oxc/type-declarations/signatures/property-with-type-import.snap @@ -31,7 +31,7 @@ input_file: crates/oxc_semantic/tests/fixtures/oxc/type-declarations/signatures/ "node": "ImportDefaultSpecifier", "references": [ { - "flags": "ReferenceFlags(Type | TSTypeQuery)", + "flags": "ReferenceFlags(Type)", "id": 0, "name": "X", "node_id": 15 diff --git a/crates/oxc_semantic/tests/fixtures/typescript-eslint/class/declaration/type-reference.snap b/crates/oxc_semantic/tests/fixtures/typescript-eslint/class/declaration/type-reference.snap index c0baec6ebdf79..03a95a3fb56c9 100644 --- a/crates/oxc_semantic/tests/fixtures/typescript-eslint/class/declaration/type-reference.snap +++ b/crates/oxc_semantic/tests/fixtures/typescript-eslint/class/declaration/type-reference.snap @@ -44,7 +44,7 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/class/declarati "node_id": 8 }, { - "flags": "ReferenceFlags(Read | TSTypeQuery)", + "flags": "ReferenceFlags(Type)", "id": 1, "name": "A", "node_id": 13 diff --git a/crates/oxc_semantic/tests/fixtures/typescript-eslint/import/type-default-value.snap b/crates/oxc_semantic/tests/fixtures/typescript-eslint/import/type-default-value.snap index 6e08802eb578b..6375fba23590c 100644 --- a/crates/oxc_semantic/tests/fixtures/typescript-eslint/import/type-default-value.snap +++ b/crates/oxc_semantic/tests/fixtures/typescript-eslint/import/type-default-value.snap @@ -24,7 +24,7 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/import/type-def "node": "ImportDefaultSpecifier", "references": [ { - "flags": "ReferenceFlags(Type | TSTypeQuery)", + "flags": "ReferenceFlags(Type)", "id": 0, "name": "foo", "node_id": 10 diff --git a/crates/oxc_semantic/tests/fixtures/typescript-eslint/import/type-inline-value.snap b/crates/oxc_semantic/tests/fixtures/typescript-eslint/import/type-inline-value.snap index 8133c64fb029c..c5daf1cd6d17c 100644 --- a/crates/oxc_semantic/tests/fixtures/typescript-eslint/import/type-inline-value.snap +++ b/crates/oxc_semantic/tests/fixtures/typescript-eslint/import/type-inline-value.snap @@ -24,7 +24,7 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/import/type-inl "node": "ImportSpecifier(foo)", "references": [ { - "flags": "ReferenceFlags(Type | TSTypeQuery)", + "flags": "ReferenceFlags(Type)", "id": 0, "name": "foo", "node_id": 11 diff --git a/crates/oxc_semantic/tests/fixtures/typescript-eslint/import/type-named-value.snap b/crates/oxc_semantic/tests/fixtures/typescript-eslint/import/type-named-value.snap index 062de274b0073..526b983cc3e07 100644 --- a/crates/oxc_semantic/tests/fixtures/typescript-eslint/import/type-named-value.snap +++ b/crates/oxc_semantic/tests/fixtures/typescript-eslint/import/type-named-value.snap @@ -24,7 +24,7 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/import/type-nam "node": "ImportSpecifier(foo)", "references": [ { - "flags": "ReferenceFlags(Type | TSTypeQuery)", + "flags": "ReferenceFlags(Type)", "id": 0, "name": "foo", "node_id": 11 diff --git a/crates/oxc_semantic/tests/fixtures/typescript-eslint/instantiation-expressions/type-arguments2.snap b/crates/oxc_semantic/tests/fixtures/typescript-eslint/instantiation-expressions/type-arguments2.snap index 90a455626ba56..fa7fbd85dfd79 100644 --- a/crates/oxc_semantic/tests/fixtures/typescript-eslint/instantiation-expressions/type-arguments2.snap +++ b/crates/oxc_semantic/tests/fixtures/typescript-eslint/instantiation-expressions/type-arguments2.snap @@ -75,7 +75,7 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/instantiation-e "node": "Function(makeBox)", "references": [ { - "flags": "ReferenceFlags(Read | TSTypeQuery)", + "flags": "ReferenceFlags(Type)", "id": 2, "name": "makeBox", "node_id": 27 diff --git a/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/function/function2.snap b/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/function/function2.snap index ca2a075f2a04d..929c6f6f887ab 100644 --- a/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/function/function2.snap +++ b/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/function/function2.snap @@ -24,7 +24,7 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaratio "node": "VariableDeclarator(arg)", "references": [ { - "flags": "ReferenceFlags(Read | TSTypeQuery)", + "flags": "ReferenceFlags(Type)", "id": 0, "name": "arg", "node_id": 15 diff --git a/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/index-access3.snap b/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/index-access3.snap index a7cacb8b0147f..82641ed0b6dc2 100644 --- a/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/index-access3.snap +++ b/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/index-access3.snap @@ -45,7 +45,7 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaratio "node": "VariableDeclarator(k)", "references": [ { - "flags": "ReferenceFlags(Read | TSTypeQuery)", + "flags": "ReferenceFlags(Type)", "id": 1, "name": "k", "node_id": 21 diff --git a/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/signatures/property-computed-name.snap b/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/signatures/property-computed-name.snap index 14f6fb3ef86e0..6e5dd94ba115e 100644 --- a/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/signatures/property-computed-name.snap +++ b/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/signatures/property-computed-name.snap @@ -31,7 +31,7 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaratio "node": "VariableDeclarator(x)", "references": [ { - "flags": "ReferenceFlags(Read | TSTypeQuery)", + "flags": "ReferenceFlags(Type)", "id": 0, "name": "x", "node_id": 14 diff --git a/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/type-query-qualified.snap b/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/type-query-qualified.snap index 3eaf6724d46d7..ff13cebf02128 100644 --- a/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/type-query-qualified.snap +++ b/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/type-query-qualified.snap @@ -31,7 +31,7 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaratio "node": "VariableDeclarator(x)", "references": [ { - "flags": "ReferenceFlags(Read | TSTypeQuery)", + "flags": "ReferenceFlags(Type)", "id": 0, "name": "x", "node_id": 21 diff --git a/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/type-query-with-parameters.snap b/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/type-query-with-parameters.snap index 4f0addfc6d088..9b2bdd129f9e4 100644 --- a/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/type-query-with-parameters.snap +++ b/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/type-query-with-parameters.snap @@ -75,7 +75,7 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaratio "node": "Function(foo)", "references": [ { - "flags": "ReferenceFlags(Read | TSTypeQuery)", + "flags": "ReferenceFlags(Type)", "id": 2, "name": "foo", "node_id": 29 diff --git a/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/type-query.snap b/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/type-query.snap index 8f991540fbca0..ec3f8da9aa199 100644 --- a/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/type-query.snap +++ b/crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaration/type-query.snap @@ -31,7 +31,7 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaratio "node": "VariableDeclarator(x)", "references": [ { - "flags": "ReferenceFlags(Read | TSTypeQuery)", + "flags": "ReferenceFlags(Type)", "id": 0, "name": "x", "node_id": 9 diff --git a/crates/oxc_syntax/src/reference.rs b/crates/oxc_syntax/src/reference.rs index 8f7adbd359159..f2b0a4a6d2a79 100644 --- a/crates/oxc_syntax/src/reference.rs +++ b/crates/oxc_syntax/src/reference.rs @@ -51,22 +51,17 @@ bitflags! { /// There are three general categories of references: /// 1. Values being referenced as values /// 2. Types being referenced as types - /// 3. Values being referenced as types + /// 3. Values being used in type contexts /// - /// ## Values - /// Reading a value is indicated by [`Read`], writing a value - /// is indicated by [`Write`]. References can be both a read - /// and a write, such as in this scenario: + /// ## Value as Type + /// The [`ValueAsType`] flag is a temporary marker for references that need to + /// resolve to value symbols initially, but will ultimately be treated as type references. + /// This flag is crucial in scenarios like TypeScript's `typeof` operator. /// - /// ```js - /// let a = 1; - /// a++; - /// ``` - /// - /// When a value symbol is used as a type, such as in `typeof a`, it has - /// [`TSTypeQuery`] added to its flags. It is, however, still - /// considered a read. A good rule of thumb is that if a reference has [`Read`] - /// or [`Write`] in its flags, it is referencing a value symbol. + /// For example, in `type T = typeof a`: + /// 1. The reference to 'a' is initially flagged with [`ValueAsType`]. + /// 2. This ensures that during symbol resolution, 'a' should be a value symbol. + /// 3. However, the final resolved reference's flags will be treated as a type. /// /// ## Types /// Type references are indicated by [`Type`]. These are used primarily in @@ -75,7 +70,8 @@ bitflags! { /// /// [`Read`]: ReferenceFlags::Read /// [`Write`]: ReferenceFlags::Write - /// [`TSTypeQuery`]: ReferenceFlags::TSTypeQuery + /// [`Type`]: ReferenceFlags::Type + /// [`ValueAsType`]: ReferenceFlags::ValueAsType #[derive(Debug, Default, Clone, Copy, Eq, PartialEq)] #[cfg_attr(feature = "serialize", derive(Serialize))] pub struct ReferenceFlags: u8 { @@ -84,10 +80,10 @@ bitflags! { const Read = 1 << 0; /// A symbol is being written to in a Value context. const Write = 1 << 1; - // Used in type definitions. + /// Used in type definitions. const Type = 1 << 2; - // Used in `typeof xx` - const TSTypeQuery = 1 << 3; + /// A value symbol is used in a type context, such as in `typeof` expressions. + const ValueAsType = 1 << 3; /// The symbol being referenced is a value. /// /// Note that this does not necessarily indicate the reference is used @@ -144,10 +140,10 @@ impl ReferenceFlags { self.contains(Self::Read | Self::Write) } - /// The identifier is used in a type referenced + /// Checks if the reference is a value being used in a type context. #[inline] - pub fn is_ts_type_query(&self) -> bool { - self.contains(Self::TSTypeQuery) + pub fn is_value_as_type(&self) -> bool { + self.contains(Self::ValueAsType) } /// The identifier is used in a type definition.