Skip to content

Commit

Permalink
perf(semantic): do not need to handle type resolving when it is not a…
Browse files Browse the repository at this point in the history
… typescript code
  • Loading branch information
Dunqing committed Jan 20, 2025
1 parent d9f5e7f commit 8711e0a
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 61 deletions.
119 changes: 72 additions & 47 deletions crates/oxc_semantic/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,59 +483,80 @@ impl<'a> SemanticBuilder<'a> {
fn resolve_references_for_current_scope(&mut self) {
let (current_refs, parent_refs) = self.unresolved_references.current_and_parent_mut();

for (name, mut references) in current_refs.drain() {
// Try to resolve a reference.
// If unresolved, transfer it to parent scope's unresolved references.
let bindings = self.scope.get_bindings(self.current_scope_id);
if let Some(symbol_id) = bindings.get(name.as_str()).copied() {
let symbol_flags = self.symbols.get_flags(symbol_id);
references.retain(|&reference_id| {
let reference = &mut self.symbols.references[reference_id];

let flags = reference.flags_mut();
if self.source_type.is_typescript() {
for (name, mut references) in current_refs.drain() {
// Try to resolve a reference.
// If unresolved, transfer it to parent scope's unresolved references.
let bindings = self.scope.get_bindings(self.current_scope_id);
if let Some(symbol_id) = bindings.get(name.as_str()).copied() {
let symbol_flags = self.symbols.get_flags(symbol_id);
references.retain(|&reference_id| {
let reference = &mut self.symbols.references[reference_id];

let flags = reference.flags_mut();

// Determine the symbol whether can be referenced by this reference.
let resolved = (flags.is_value()
&& symbol_flags.can_be_referenced_by_value())
|| (flags.is_type() && symbol_flags.can_be_referenced_by_type())
|| (flags.is_value_as_type()
&& symbol_flags.can_be_referenced_by_value_as_type());

if !resolved {
return true;
}

// Determine the symbol whether can be referenced by this reference.
let resolved = (flags.is_value() && symbol_flags.can_be_referenced_by_value())
|| (flags.is_type() && symbol_flags.can_be_referenced_by_type())
|| (flags.is_value_as_type()
&& symbol_flags.can_be_referenced_by_value_as_type());
if symbol_flags.is_value() && flags.is_value() {
// The non type-only ExportSpecifier can reference both type/value symbols,
// if the symbol is a value symbol and reference flag is not type-only,
// remove the type flag. For example: `const B = 1; export { B };`
*flags -= ReferenceFlags::Type;
} else {
// 1. ReferenceFlags::ValueAsType -> ReferenceFlags::Type
// `const ident = 0; typeof ident`
// ^^^^^ -> The ident is a value symbols,
// but it used as a type.
// 2. ReferenceFlags::Value | ReferenceFlags::Type -> ReferenceFlags::Type
// `type ident = string; export default ident;
// ^^^^^ We have confirmed the symbol is
// not a value symbol, so we need to
// make sure the reference is a type only.
*flags = ReferenceFlags::Type;
}
reference.set_symbol_id(symbol_id);
self.symbols.add_resolved_reference(symbol_id, reference_id);

if !resolved {
return true;
}
false
});

if symbol_flags.is_value() && flags.is_value() {
// The non type-only ExportSpecifier can reference both type/value symbols,
// if the symbol is a value symbol and reference flag is not type-only,
// remove the type flag. For example: `const B = 1; export { B };`
*flags -= ReferenceFlags::Type;
} else {
// 1. ReferenceFlags::ValueAsType -> ReferenceFlags::Type
// `const ident = 0; typeof ident`
// ^^^^^ -> The ident is a value symbols,
// but it used as a type.
// 2. ReferenceFlags::Value | ReferenceFlags::Type -> ReferenceFlags::Type
// `type ident = string; export default ident;
// ^^^^^ We have confirmed the symbol is
// not a value symbol, so we need to
// make sure the reference is a type only.
*flags = ReferenceFlags::Type;
if references.is_empty() {
continue;
}
reference.set_symbol_id(symbol_id);
self.symbols.add_resolved_reference(symbol_id, reference_id);

false
});
}

if references.is_empty() {
continue;
if let Some(parent_reference_ids) = parent_refs.get_mut(&name) {
parent_reference_ids.extend(references);
} else {
parent_refs.insert(name, references);
}
}

if let Some(parent_reference_ids) = parent_refs.get_mut(&name) {
parent_reference_ids.extend(references);
} else {
parent_refs.insert(name, references);
} else {
let bindings = self.scope.get_bindings(self.current_scope_id);
for (name, references) in current_refs.drain() {
// Try to resolve a reference.
// If unresolved, transfer it to parent scope's unresolved references.

if let Some(symbol_id) = bindings.get(name.as_str()).copied() {
for reference_id in &references {
let reference = self.symbols.get_reference_mut(*reference_id);
reference.set_symbol_id(symbol_id);
}
self.symbols.extend_resolved_reference_ids(symbol_id, references);
} else if let Some(parent_reference_ids) = parent_refs.get_mut(&name) {
parent_reference_ids.extend(references);
} else {
parent_refs.insert(name, references);
}
}
}
}
Expand Down Expand Up @@ -1843,7 +1864,9 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
ExportDefaultDeclarationKind::Identifier(it) => {
// `export default ident`
// ^^^^^ -> can reference both type/value symbols
self.current_reference_flags = ReferenceFlags::Read | ReferenceFlags::Type;
if self.source_type.is_typescript() {
self.current_reference_flags = ReferenceFlags::Read | ReferenceFlags::Type;
}
self.visit_identifier_reference(it);
}
match_expression!(ExportDefaultDeclarationKind) => {
Expand All @@ -1863,6 +1886,8 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
if let Some(source) = &it.source {
self.visit_string_literal(source);
self.visit_export_specifiers(&it.specifiers);
} else if !self.source_type.is_typescript() {
self.visit_export_specifiers(&it.specifiers);
} else {
for specifier in &it.specifiers {
// `export type { a }` or `export { type a }` -> `a` is a type reference
Expand Down
10 changes: 10 additions & 0 deletions crates/oxc_semantic/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,16 @@ impl SymbolTable {
.map(|&reference_id| &self.references[reference_id])
}

pub fn extend_resolved_reference_ids(
&mut self,
symbol_id: SymbolId,
reference_ids: Vec<ReferenceId>,
) {
self.inner.with_dependent_mut(|_, inner| {
inner.resolved_references[symbol_id.index()].extend(reference_ids);
});
}

/// Get whether a symbol is mutated (i.e. assigned to).
///
/// If symbol is `const`, always returns `false`.
Expand Down
14 changes: 12 additions & 2 deletions tasks/transform_conformance/snapshots/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
commit: acbc09a8

Passed: 698/1168
Passed: 696/1168

# All Passed:
* babel-plugin-transform-logical-assignment-operators
Expand Down Expand Up @@ -1355,7 +1355,7 @@ x Output mismatch
x Output mismatch


# babel-plugin-transform-typescript (49/159)
# babel-plugin-transform-typescript (47/159)
* cast/as-expression/input.ts
Unresolved references mismatch:
after transform: ["T", "x"]
Expand Down Expand Up @@ -1728,6 +1728,16 @@ Symbol reference IDs mismatch for "None":
after transform: SymbolId(0): [ReferenceId(0), ReferenceId(2)]
rebuilt : SymbolId(0): [ReferenceId(1)]

* exports/export-context-variables/input.ts
Reference flags mismatch for "undefined":
after transform: ReferenceId(0): ReferenceFlags(Read | Type)
rebuilt : ReferenceId(0): ReferenceFlags(Read)

* exports/export-globals/input.ts
Reference flags mismatch for "Math":
after transform: ReferenceId(0): ReferenceFlags(Read | Type)
rebuilt : ReferenceId(0): ReferenceFlags(Read)

* exports/export-type/input.ts
Scope children mismatch:
after transform: ScopeId(0): [ScopeId(1)]
Expand Down
15 changes: 7 additions & 8 deletions tasks/transform_conformance/snapshots/babel_exec.snap.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
commit: acbc09a8

node: v22.12.0
[?25l

Passed: 318 of 406 (78.33%)

Failures:
Expand Down Expand Up @@ -61,8 +61,8 @@ TypeError: e.has is not a function

./fixtures/babel/babel-plugin-transform-class-properties-test-fixtures-public-computed-toPrimitive-exec.test.js
AssertionError: expected [Function] to throw error including '@@toPrimitive must return a primitive…' but got 'Cannot convert object to primitive va…'
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest+expect@3.0.2/node_modules/@vitest/expect/dist/index.js:1648:21)
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest+expect@3.0.2/node_modules/@vitest/expect/dist/index.js:1037:17)
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest+expect@2.1.2/node_modules/@vitest/expect/dist/index.js:1438:21)
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest+expect@2.1.2/node_modules/@vitest/expect/dist/index.js:923:17)
at Proxy.methodWrapper (./node_modules/.pnpm/[email protected]/node_modules/chai/chai.js:1610:25)
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-class-properties-test-fixtures-public-computed-toPrimitive-exec.test.js:37:5

Expand Down Expand Up @@ -430,8 +430,8 @@ ReferenceError: _Foo_brand is not defined

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-private-loose-rhs-not-object-exec.test.js
AssertionError: expected [Function] to throw error including 'right-hand side of \'in\' should be a…' but got '_Class_brand is not defined'
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest+expect@3.0.2/node_modules/@vitest/expect/dist/index.js:1648:21)
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest+expect@3.0.2/node_modules/@vitest/expect/dist/index.js:1037:17)
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest+expect@2.1.2/node_modules/@vitest/expect/dist/index.js:1438:21)
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest+expect@2.1.2/node_modules/@vitest/expect/dist/index.js:923:17)
at Proxy.methodWrapper (./node_modules/.pnpm/[email protected]/node_modules/chai/chai.js:1610:25)
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-private-loose-rhs-not-object-exec.test.js:176:5

Expand Down Expand Up @@ -461,8 +461,8 @@ ReferenceError: transformAsync is not defined

./fixtures/babel/babel-preset-env-test-fixtures-plugins-integration-issue-15170-exec.test.js
AssertionError: expected [Function] to not throw an error but 'ReferenceError: x is not defined' was thrown
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest+expect@3.0.2/node_modules/@vitest/expect/dist/index.js:1648:21)
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest+expect@3.0.2/node_modules/@vitest/expect/dist/index.js:1037:17)
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest+expect@2.1.2/node_modules/@vitest/expect/dist/index.js:1438:21)
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest+expect@2.1.2/node_modules/@vitest/expect/dist/index.js:923:17)
at Proxy.methodWrapper (./node_modules/.pnpm/[email protected]/node_modules/chai/chai.js:1610:25)
at ./tasks/transform_conformance/fixtures/babel/babel-preset-env-test-fixtures-plugins-integration-issue-15170-exec.test.js:6:9

Expand All @@ -473,4 +473,3 @@ TypeError: Assignment to constant variable.
./fixtures/babel/babel-preset-env-test-fixtures-sanity-regex-dot-all-exec.test.js
AssertionError: expected false to be true // Object.is equality
at ./tasks/transform_conformance/fixtures/babel/babel-preset-env-test-fixtures-sanity-regex-dot-all-exec.test.js:10:37
[?25h
7 changes: 3 additions & 4 deletions tasks/transform_conformance/snapshots/oxc_exec.snap.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
commit: acbc09a8

node: v22.12.0
[?25l

Passed: 5 of 7 (71.43%)

Failures:

./fixtures/oxc/babel-plugin-transform-class-properties-test-fixtures-private-field-resolve-to-method-in-computed-key-exec.test.js
AssertionError: expected [Function] to throw error including 'Receiver must be an instance of class…' but got 'Private element is not present on thi…'
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest+expect@3.0.2/node_modules/@vitest/expect/dist/index.js:1648:21)
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest+expect@3.0.2/node_modules/@vitest/expect/dist/index.js:1037:17)
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest+expect@2.1.2/node_modules/@vitest/expect/dist/index.js:1438:21)
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest+expect@2.1.2/node_modules/@vitest/expect/dist/index.js:923:17)
at Proxy.methodWrapper (./node_modules/.pnpm/[email protected]/node_modules/chai/chai.js:1610:25)
at ./tasks/transform_conformance/fixtures/oxc/babel-plugin-transform-class-properties-test-fixtures-private-field-resolve-to-method-in-computed-key-exec.test.js:96:33

./fixtures/oxc/babel-plugin-transform-class-properties-test-fixtures-static-super-tagged-template-exec.test.js
AssertionError: expected undefined to be [Function C] // Object.is equality
at ./tasks/transform_conformance/fixtures/oxc/babel-plugin-transform-class-properties-test-fixtures-static-super-tagged-template-exec.test.js:15:17
[?25h

0 comments on commit 8711e0a

Please sign in to comment.