mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
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:
parent
d8b9909bd8
commit
e4ed41d4fa
15 changed files with 44 additions and 51 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue