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.
This commit is contained in:
Dunqing 2024-09-05 01:50:20 +00:00
parent d8b9909bd8
commit e4ed41d4fa
15 changed files with 44 additions and 51 deletions

View file

@ -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<T> = typeof a<T>;
// ^^^ 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;
}
}

View file

@ -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()
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.