Skip to content

Commit

Permalink
feat: support explicit-resource-management (#5444)
Browse files Browse the repository at this point in the history
* feat: support using keyword

* Tweak test, update swc_ecma_parser version

* Detect 'using' in VariableDeclaration
  • Loading branch information
TrickyPi committed Apr 3, 2024
1 parent c87901d commit 8521d29
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 38 deletions.
70 changes: 46 additions & 24 deletions rust/parse_ast/src/convert_ast/converter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use swc_ecma_ast::{
PropName, PropOrSpread, Regex, RestPat, ReturnStmt, SeqExpr, SetterProp, SimpleAssignTarget,
SpreadElement, StaticBlock, Stmt, Str, Super, SuperProp, SuperPropExpr, SwitchCase, SwitchStmt,
TaggedTpl, ThisExpr, ThrowStmt, Tpl, TplElement, TryStmt, UnaryExpr, UnaryOp, UpdateExpr,
UpdateOp, VarDecl, VarDeclKind, VarDeclOrExpr, VarDeclarator, WhileStmt, YieldExpr,
UpdateOp, UsingDecl, VarDecl, VarDeclKind, VarDeclOrExpr, VarDeclarator, WhileStmt, YieldExpr,
};

use crate::convert_ast::annotations::{AnnotationKind, AnnotationWithType};
Expand Down Expand Up @@ -259,14 +259,18 @@ impl<'a> AstConverter<'a> {

fn convert_declaration(&mut self, declaration: &Decl) {
match declaration {
Decl::Var(variable_declaration) => self.convert_variable_declaration(variable_declaration),
Decl::Var(variable_declaration) => {
self.convert_variable_declaration(&VariableDeclaration::Var(variable_declaration))
}
Decl::Fn(function_declaration) => self.convert_function(
&function_declaration.function,
&TYPE_FUNCTION_DECLARATION_INLINED_ANNOTATIONS,
Some(&function_declaration.ident),
),
Decl::Class(class_declaration) => self.convert_class_declaration(class_declaration),
Decl::Using(_) => unimplemented!("Cannot convert Decl::Using"),
Decl::Using(using_declaration) => {
self.convert_variable_declaration(&VariableDeclaration::Using(using_declaration))
}
Decl::TsInterface(_) => unimplemented!("Cannot convert Decl::TsInterface"),
Decl::TsTypeAlias(_) => unimplemented!("Cannot convert Decl::TsTypeAlias"),
Decl::TsEnum(_) => unimplemented!("Cannot convert Decl::TsEnum"),
Expand Down Expand Up @@ -527,12 +531,14 @@ impl<'a> AstConverter<'a> {
fn convert_for_head(&mut self, for_head: &ForHead) {
match for_head {
ForHead::VarDecl(variable_declaration) => {
self.convert_variable_declaration(variable_declaration)
self.convert_variable_declaration(&VariableDeclaration::Var(variable_declaration))
}
ForHead::Pat(pattern) => {
self.convert_pattern(pattern);
}
ForHead::UsingDecl(_) => unimplemented!("Cannot convert ForHead::UsingDecl"),
ForHead::UsingDecl(using_declaration) => {
self.convert_variable_declaration(&VariableDeclaration::Using(using_declaration))
}
}
}

Expand Down Expand Up @@ -995,7 +1001,7 @@ impl<'a> AstConverter<'a> {
) {
match variable_declaration_or_expression {
VarDeclOrExpr::VarDecl(variable_declaration) => {
self.convert_variable_declaration(variable_declaration);
self.convert_variable_declaration(&VariableDeclaration::Var(variable_declaration));
}
VarDeclOrExpr::Expr(expression) => {
self.convert_expression(expression);
Expand Down Expand Up @@ -2942,32 +2948,43 @@ impl<'a> AstConverter<'a> {
self.add_end(end_position, &update_expression.span);
}

fn convert_variable_declaration(&mut self, variable_declaration: &VarDecl) {
fn convert_variable_declaration(&mut self, variable_declaration: &VariableDeclaration) {
let (kind, span, decls): (&[u8; 4], Span, &Vec<VarDeclarator>) = match variable_declaration {
VariableDeclaration::Var(value) => (
match value.kind {
VarDeclKind::Var => &STRING_VAR,
VarDeclKind::Let => &STRING_LET,
VarDeclKind::Const => &STRING_CONST,
},
value.span,
&value.decls,
),
&VariableDeclaration::Using(value) => (
if value.is_await {
&STRING_AWAIT_USING
} else {
&STRING_USING
},
value.span,
&value.decls,
),
};
let end_position = self.add_type_and_start(
&TYPE_VARIABLE_DECLARATION_INLINED_DECLARATIONS,
&variable_declaration.span,
&span,
VARIABLE_DECLARATION_RESERVED_BYTES,
matches!(variable_declaration.kind, VarDeclKind::Const),
kind == &STRING_CONST,
);
// declarations
self.convert_item_list(
&variable_declaration.decls,
|ast_converter, variable_declarator| {
ast_converter.convert_variable_declarator(variable_declarator);
true
},
);
self.convert_item_list(decls, |ast_converter, variable_declarator| {
ast_converter.convert_variable_declarator(variable_declarator);
true
});
// kind
let kind_position = end_position + VARIABLE_DECLARATION_KIND_OFFSET;
self.buffer[kind_position..kind_position + 4].copy_from_slice(
match variable_declaration.kind {
VarDeclKind::Var => &STRING_VAR,
VarDeclKind::Let => &STRING_LET,
VarDeclKind::Const => &STRING_CONST,
},
);
self.buffer[kind_position..kind_position + 4].copy_from_slice(kind);
// end
self.add_end(end_position, &variable_declaration.span);
self.add_end(end_position, &span);
}

fn convert_variable_declarator(&mut self, variable_declarator: &VarDeclarator) {
Expand Down Expand Up @@ -3065,6 +3082,11 @@ pub fn convert_string(buffer: &mut Vec<u8>, string: &str) {
buffer.resize(buffer.len() + additional_length, 0);
}

enum VariableDeclaration<'a> {
Var(&'a VarDecl),
Using(&'a UsingDecl),
}

enum StoredCallee<'a> {
Expression(&'a Expr),
Super(&'a Super),
Expand Down
2 changes: 2 additions & 0 deletions rust/parse_ast/src/convert_ast/converter/string_constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,5 @@ pub const STRING_NULLISHASSIGN: [u8; 4] = 57u32.to_ne_bytes(); // ??=
pub const STRING_PURE: [u8; 4] = 58u32.to_ne_bytes(); // pure
pub const STRING_NOSIDEEFFECTS: [u8; 4] = 59u32.to_ne_bytes(); // noSideEffects
pub const STRING_SOURCEMAP: [u8; 4] = 60u32.to_ne_bytes(); // sourcemap
pub const STRING_USING: [u8; 4] = 61u32.to_ne_bytes(); // using
pub const STRING_AWAIT_USING: [u8; 4] = 62u32.to_ne_bytes(); // await using
1 change: 1 addition & 0 deletions rust/parse_ast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub fn parse_ast(code: String, allow_return_outside_function: bool) -> Vec<u8> {
let syntax = Syntax::Es(EsConfig {
allow_return_outside_function,
import_attributes: true,
explicit_resource_management: true,
..Default::default()
});

Expand Down
12 changes: 4 additions & 8 deletions src/ast/nodes/Identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,7 @@ import type { VariableKind } from './shared/VariableKinds';

export type IdentifierWithVariable = Identifier & { variable: Variable };

const tdzVariableKinds = {
__proto__: null,
class: true,
const: true,
let: true,
var: true
};
const tdzVariableKinds = new Set(['class', 'const', 'let', 'var', 'using', 'await using']);

export default class Identifier extends NodeBase implements PatternNode {
declare name: string;
Expand Down Expand Up @@ -92,6 +86,8 @@ export default class Identifier extends NodeBase implements PatternNode {
}
case 'let':
case 'const':
case 'using':
case 'await using':
case 'class': {
variable = this.scope.addDeclaration(this, this.scope.context, init, kind);
break;
Expand Down Expand Up @@ -221,7 +217,7 @@ export default class Identifier extends NodeBase implements PatternNode {
!(
this.variable instanceof LocalVariable &&
this.variable.kind &&
this.variable.kind in tdzVariableKinds &&
tdzVariableKinds.has(this.variable.kind) &&
// we ignore possible TDZs due to circular module dependencies as
// otherwise we get many false positives
this.variable.module === this.scope.context.module
Expand Down
5 changes: 4 additions & 1 deletion src/ast/nodes/VariableDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export default class VariableDeclaration extends NodeBase {
declare declarations: readonly VariableDeclarator[];
declare kind: VariableDeclarationKind;
declare type: NodeType.tVariableDeclaration;
declare isUsingDeclaration: boolean;

deoptimizePath(): void {
for (const declarator of this.declarations) {
Expand Down Expand Up @@ -82,8 +83,9 @@ export default class VariableDeclaration extends NodeBase {

initialise(): void {
super.initialise();
this.isUsingDeclaration = this.kind === 'await using' || this.kind === 'using';
for (const declarator of this.declarations) {
declarator.declareDeclarator(this.kind);
declarator.declareDeclarator(this.kind, this.isUsingDeclaration);
}
}

Expand All @@ -97,6 +99,7 @@ export default class VariableDeclaration extends NodeBase {
nodeRenderOptions: NodeRenderOptions = BLANK
): void {
if (
this.isUsingDeclaration ||
areAllDeclarationsIncludedAndNotExported(this.declarations, options.exportNamesByVariable)
) {
for (const declarator of this.declarations) {
Expand Down
8 changes: 5 additions & 3 deletions src/ast/nodes/VariableDeclarator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ export default class VariableDeclarator extends NodeBase {
declare id: PatternNode;
declare init: ExpressionNode | null;
declare type: NodeType.tVariableDeclarator;
declare isUsingDeclaration: boolean;

declareDeclarator(kind: VariableKind): void {
declareDeclarator(kind: VariableKind, isUsingDeclaration: boolean): void {
this.isUsingDeclaration = isUsingDeclaration;
this.id.declare(kind, this.init || UNDEFINED_EXPRESSION);
}

Expand All @@ -33,7 +35,7 @@ export default class VariableDeclarator extends NodeBase {
if (!this.deoptimized) this.applyDeoptimizations();
const initEffect = this.init?.hasEffects(context);
this.id.markDeclarationReached();
return initEffect || this.id.hasEffects(context);
return initEffect || this.id.hasEffects(context) || this.isUsingDeclaration;
}

include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
Expand All @@ -57,7 +59,7 @@ export default class VariableDeclarator extends NodeBase {
snippets: { _, getPropertyAccess }
} = options;
const { end, id, init, start } = this;
const renderId = id.included;
const renderId = id.included || this.isUsingDeclaration;
if (renderId) {
id.render(code, options);
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/ast/nodes/shared/VariableKinds.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export type VariableDeclarationKind = 'var' | 'let' | 'const';
export type VariableDeclarationKind = 'var' | 'let' | 'const' | 'using' | 'await using';
export type VariableKind = VariableDeclarationKind | 'function' | 'class' | 'parameter' | 'other';
5 changes: 4 additions & 1 deletion src/utils/convert-ast-strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,8 @@ export const FIXED_STRINGS = [
'||=',
'??=',
'pure',
'noSideEffects'
'noSideEffects',
'sourcemap',
'using',
'await using'
];
4 changes: 4 additions & 0 deletions test/form/samples/support-using-keyword/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = defineTest({
description: 'Support `using` keyword',
verifyAst: false
});
22 changes: 22 additions & 0 deletions test/form/samples/support-using-keyword/_expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const logClean = function () {
return {
[Symbol.dispose]() {
console.log('clean in sync');
},
[Symbol.asyncDispose]() {
console.log('clean in async');
}
};
};

async function foo() {
using a = logClean();
await using b = logClean();
for (using a of [logClean(), logClean()]) {
}

for (await using a of [logClean(), logClean()]) {
}
}

foo();
25 changes: 25 additions & 0 deletions test/form/samples/support-using-keyword/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const logClean = function () {
return {
[Symbol.dispose]() {
console.log('clean in sync');
},
[Symbol.asyncDispose]() {
console.log('clean in async');
}
};
};

async function foo() {
using a = logClean();
const aa = a;

await using b = logClean();

for (using a of [logClean(), logClean()]) {
}

for (await using a of [logClean(), logClean()]) {
}
}

foo();

0 comments on commit 8521d29

Please sign in to comment.