From 986559878287c38e5a426237a3d164a5a41ee351 Mon Sep 17 00:00:00 2001 From: Hugo Heuzard Date: Mon, 13 Nov 2023 22:38:01 +0100 Subject: [PATCH] Compiler: fix es6 scopes --- compiler/bin-jsoo_minify/jsoo_minify.ml | 2 +- compiler/lib/js_traverse.ml | 158 +++++++++++++++++++++--- compiler/lib/js_traverse.mli | 14 ++- compiler/lib/stdlib.ml | 4 + compiler/tests-compiler/scopes.ml | 60 ++++++++- 5 files changed, 216 insertions(+), 22 deletions(-) diff --git a/compiler/bin-jsoo_minify/jsoo_minify.ml b/compiler/bin-jsoo_minify/jsoo_minify.ml index 80fcc9f74d..22ffdb132c 100644 --- a/compiler/bin-jsoo_minify/jsoo_minify.ml +++ b/compiler/bin-jsoo_minify/jsoo_minify.ml @@ -81,7 +81,7 @@ let f { Cmd_arg.common; output_file; use_stdin; files } = let true_ () = true in let open Config in let passes : ((unit -> bool) * (unit -> Js_traverse.mapper)) list = - [ (Flag.shortvar, fun () -> new Js_traverse.rename_variable) + [ (Flag.shortvar, fun () -> (new Js_traverse.rename_variable :> Js_traverse.mapper)) ; (true_, fun () -> new Js_traverse.simpl) ; (true_, fun () -> new Js_traverse.clean) ] diff --git a/compiler/lib/js_traverse.ml b/compiler/lib/js_traverse.ml index 32413be9cc..32c06b7398 100644 --- a/compiler/lib/js_traverse.ml +++ b/compiler/lib/js_traverse.ml @@ -35,6 +35,8 @@ class type mapper = object method class_decl : Javascript.class_declaration -> Javascript.class_declaration + method class_element : Javascript.class_element -> Javascript.class_element + method initialiser : Javascript.expression * Javascript.location -> Javascript.expression * Javascript.location @@ -108,7 +110,7 @@ class map : mapper = ; body = List.map x.body ~f:m#class_element } - method private class_element x = + method class_element x = match x with | CEMethod (s, n, meth) -> CEMethod (s, m#class_element_name n, m#method_ meth) | CEField (s, n, i) -> CEField (s, m#class_element_name n, m#initialiser_o i) @@ -305,6 +307,8 @@ class map : mapper = class type iterator = object method fun_decl : Javascript.function_declaration -> unit + method class_decl : Javascript.class_declaration -> unit + method early_error : Javascript.early_error -> unit method expression : Javascript.expression -> unit @@ -373,7 +377,7 @@ class iter : iterator = m#formal_parameter_list params; m#function_body body - method private class_decl x = + method class_decl x = Option.iter x.extends ~f:m#expression; List.iter x.body ~f:m#class_element @@ -843,14 +847,19 @@ class free = m#merge_info tbody; EFun (ident, (k, params, body, nid)) | EClass (ident_o, cl_decl) -> + let same_level = level in + let cbody = {} in let ident_o = Option.map ~f:(fun id -> - m#def_var id; - m#ident id) + cbody#def_var id; + id) ident_o in - EClass (ident_o, m#class_decl cl_decl) + let cl_decl = cbody#class_decl cl_decl in + cbody#record_block Normal; + m#merge_block_info cbody; + EClass (ident_o, cl_decl) | _ -> super#expression x method record_block _ = () @@ -870,6 +879,16 @@ class free = m#merge_block_info tbody; b + method class_element x = + match x with + | CEStaticBLock l -> + let tbody = {} in + let l = tbody#statements l in + tbody#record_block Normal; + m#merge_info tbody; + CEStaticBLock l + | _ -> super#class_element x + method statement x = match x with | Function_declaration (id, (k, params, body, nid)) -> @@ -883,9 +902,56 @@ class free = m#merge_info tbody; Function_declaration (id, (k, params, body, nid)) | Class_declaration (id, cl_decl) -> + let same_level = level in + let cbody = {} in + let cl_decl = cbody#class_decl cl_decl in + cbody#record_block Normal; + m#merge_block_info cbody; m#def_var id; - Class_declaration (id, m#class_decl cl_decl) + Class_declaration (id, cl_decl) | Block b -> Block (m#block b) + | For_statement (Right (((Const | Let) as k), l), e1, e2, (st, loc)) -> + let same_level = level in + let m' = {} in + let l = List.map ~f:(m'#variable_declaration k) l in + let e1 = Option.map ~f:m'#expression e1 in + let e2 = Option.map ~f:m'#expression e2 in + let st = m'#statement st in + m'#record_block Normal; + m#merge_block_info m'; + For_statement (Right (k, l), e1, e2, (st, m#loc loc)) + | ForIn_statement (Right (((Const | Let) as k), l), e2, (st, loc)) -> + let same_level = level in + let m' = {} in + let l = m'#for_binding k l in + let e2 = m'#expression e2 in + let st = m'#statement st in + m'#record_block Normal; + m#merge_block_info m'; + ForIn_statement (Right (k, l), e2, (st, m#loc loc)) + | ForOf_statement (Right (((Const | Let) as k), l), e2, (st, loc)) -> + let same_level = level in + let m' = {} in + let l = m'#for_binding k l in + let e2 = m'#expression e2 in + let st = m'#statement st in + m'#record_block Normal; + m#merge_block_info m'; + ForOf_statement (Right (k, l), e2, (st, m#loc loc)) + | Switch_statement (e, l, def, l') -> + let same_level = level in + let m' = {} in + let l = List.map l ~f:(fun (e, s) -> m'#switch_case e, m'#statements s) in + let l' = List.map l' ~f:(fun (e, s) -> m'#switch_case e, m'#statements s) in + let def = + match def with + | None -> None + | Some l -> Some (m'#statements l) + in + let e = m#expression e in + m'#record_block Normal; + m#merge_block_info m'; + Switch_statement (e, l, def, l') | Try_statement (b, w, f) -> let same_level = level in let b = m#block b in @@ -958,24 +1024,38 @@ class rename_variable = inherit iter as super - method expression e = - match e with - | EClass (ido, _) -> - Option.iter ido ~f:decl_var; - super#expression e - | _ -> super#expression e + method expression _ = () method fun_decl _ = () + method class_decl _ = () + method statement x = match scope, x with | Fun_block _, Function_declaration (id, fd) -> decl_var id; self#fun_decl fd | Lexical_block, Function_declaration (_, fd) -> self#fun_decl fd - | (Fun_block _ | Lexical_block), Class_declaration (id, _) -> + | (Lexical_block | Fun_block _), Class_declaration (id, cl_decl) -> decl_var id; - super#statement x + self#class_decl cl_decl + | _, For_statement (Right (((Const | Let) as k), l), _e1, _e2, (st, _loc)) -> + let m = {} in + List.iter ~f:(m#variable_declaration k) l; + m#statement st + | _, ForOf_statement (Right (((Const | Let) as k), l), _e2, (st, _loc)) -> + let m = {} in + m#for_binding k l; + m#statement st + | _, ForIn_statement (Right (((Const | Let) as k), l), _e2, (st, _loc)) -> + let m = {} in + m#for_binding k l; + m#statement st + | _, Switch_statement (_, l, def, l') -> + let m = {} in + List.iter l ~f:(fun (_, s) -> m#statements s); + Option.iter def ~f:(fun l -> m#statements l); + List.iter l' ~f:(fun (_, s) -> m#statements s) | (Fun_block _ | Lexical_block), _ -> super#statement x method variable_declaration k l = @@ -1016,7 +1096,7 @@ class rename_variable = val labels = StringMap.empty - method private update_state scope params iter_body = + method update_state scope params iter_body = let declared_names = declared scope params iter_body in { StringMap.add name (Code.Var.fresh_n name) subst) @@ -1030,6 +1110,13 @@ class rename_variable = | S { name = Utf8 name; _ } -> ( try V (StringMap.find name subst) with Not_found -> x) + method class_element x = + match x with + | CEStaticBLock l -> + let m' = m#update_state (Fun_block None) [] l in + CEStaticBLock (m'#statements l) + | _ -> super#class_element x + method fun_decl (k, params, body, nid) = let ids = bound_idents_of_params params in let m' = m#update_state (Fun_block None) ids body in @@ -1047,6 +1134,9 @@ class rename_variable = EFun ( Option.map ident ~f:m'#ident , (k, m'#formal_parameter_list params, m'#function_body body, m#loc nid) ) + | EClass (Some id, cl_decl) -> + let m' = m#update_state Lexical_block [ id ] [] in + EClass (Some (m'#ident id), m'#class_decl cl_decl) | _ -> super#expression e method statement s = @@ -1081,6 +1171,28 @@ class rename_variable = Function_declaration ( m#ident id , (k, m'#formal_parameter_list params, m'#function_body body, m#loc nid) ) + | For_statement (Right (((Const | Let) as k), l), e1, e2, (st, loc)) -> + let ids = List.concat_map ~f:bound_idents_of_variable_declaration l in + let m' = m#update_state Lexical_block ids [] in + For_statement + ( Right (k, List.map ~f:(m'#variable_declaration k) l) + , Option.map ~f:m'#expression e1 + , Option.map ~f:m'#expression e2 + , (m'#statement st, m'#loc loc) ) + | ForOf_statement (Right (((Const | Let) as k), l), e2, (st, loc)) -> + let ids = bound_idents_of_binding l in + let m' = m#update_state Lexical_block ids [] in + ForOf_statement + ( Right (k, m'#for_binding k l) + , m'#expression e2 + , (m'#statement st, m'#loc loc) ) + | ForIn_statement (Right (((Const | Let) as k), l), e2, (st, loc)) -> + let ids = bound_idents_of_binding l in + let m' = m#update_state Lexical_block ids [] in + ForOf_statement + ( Right (k, m'#for_binding k l) + , m'#expression e2 + , (m'#statement st, m'#loc loc) ) | Block l -> let m' = m#update_state Lexical_block [] l in Block (m'#statements l) @@ -1124,6 +1236,22 @@ class rename_variable = Some (i, m'#statements catch) in Try_statement (block, catch, final) + | Switch_statement (e, l, def, l') -> + let all = + let r = ref [] in + Option.iter def ~f:(fun l -> r := List.rev_append l !r); + List.iter l ~f:(fun (_, s) -> r := List.rev_append s !r); + List.iter l' ~f:(fun (_, s) -> r := List.rev_append s !r); + !r + in + let m' = m#update_state Lexical_block [] all in + Switch_statement + ( m#expression e + , List.map l ~f:(fun (e, s) -> m'#switch_case e, m'#statements s) + , (match def with + | None -> None + | Some l -> Some (m'#statements l)) + , List.map l' ~f:(fun (e, s) -> m'#switch_case e, m'#statements s) ) | _ -> super#statement s end diff --git a/compiler/lib/js_traverse.mli b/compiler/lib/js_traverse.mli index ba625bab20..f931214e16 100644 --- a/compiler/lib/js_traverse.mli +++ b/compiler/lib/js_traverse.mli @@ -34,6 +34,8 @@ class type mapper = object method class_decl : Javascript.class_declaration -> Javascript.class_declaration + method class_element : Javascript.class_element -> Javascript.class_element + method initialiser : expression * location -> expression * location method initialiser_o : (expression * location) option -> (expression * location) option @@ -67,6 +69,8 @@ end class type iterator = object method fun_decl : Javascript.function_declaration -> unit + method class_decl : Javascript.class_declaration -> unit + method early_error : Javascript.early_error -> unit method expression : Javascript.expression -> unit @@ -147,7 +151,15 @@ end class free : freevar -class rename_variable : mapper +type scope = + | Lexical_block + | Fun_block of ident option + +class rename_variable : object ('a) + inherit mapper + + method update_state : scope -> Javascript.ident list -> Javascript.statement_list -> 'a +end class share_constant : mapper diff --git a/compiler/lib/stdlib.ml b/compiler/lib/stdlib.ml index ef8aeddca2..4dc3322260 100644 --- a/compiler/lib/stdlib.ml +++ b/compiler/lib/stdlib.ml @@ -347,6 +347,10 @@ module Option = struct | None -> None | Some v -> Some (f v) + let to_list = function + | None -> [] + | Some x -> [ x ] + let bind ~f x = match x with | None -> None diff --git a/compiler/tests-compiler/scopes.ml b/compiler/tests-compiler/scopes.ml index e5ebb576c8..0780231362 100644 --- a/compiler/tests-compiler/scopes.ml +++ b/compiler/tests-compiler/scopes.ml @@ -50,7 +50,7 @@ let%expect_test "let inside forloop" = 1: (function(){ 2: let v1 = 2; 3: var v2 = 0; - 4: for(let v1 = 0; v1 < 2; v1++) v2 += v1; + 4: for(let v3 = 0; v3 < 2; v3++) v2 += v3; 5: console.log(v2); 6: console.log(v1); 7: } @@ -79,7 +79,7 @@ let%expect_test "let inside forin" = 1: (function(){ 2: let v2 = 2; 3: var v3 = 0, v1 = [1, 2, 3]; - 4: for(let v2 in v1){console.log(v2); v3 += v1[v2];} + 4: for(let v4 of v1){console.log(v4); v3 += v1[v4];} 5: console.log(v3); 6: console.log(v2); 7: } @@ -110,7 +110,7 @@ let%expect_test "let inside forof" = 1: (function(){ 2: let v1 = 2; 3: var v2 = 0; - 4: for(let v1 of [1, 2, 3]){console.log(v1); v2 += v1;} + 4: for(let v3 of [1, 2, 3]){console.log(v3); v2 += v3;} 5: console.log(v2); 6: console.log(v1); 7: } @@ -141,7 +141,7 @@ let%expect_test "let inside switch" = 1: (function(){ 2: let v1 = 2; 3: var v2 = 0; - 4: switch(v2){case 0: let v1 = 3; console.log(v1); + 4: switch(v2){case 0: let v3 = 3; console.log(v3); 5: } 6: console.log(v1); 7: } @@ -179,7 +179,7 @@ let%expect_test "let and var inside class static block" = 6: z 7: = 8: 2 - 9: static{let v2 = 3; var v3 = v2; this.z = v3;} + 9: static{let v5 = 3; var v6 = v5; this.z = v6;} 10: getZ(){return this.z;} 11: } 12: var v1 = new v4; @@ -188,6 +188,56 @@ let%expect_test "let and var inside class static block" = 15: ()); 0 2 3 |}] +let%expect_test "named class expression" = + test + {| +(function () { + var y = 0; + class z { + static z = 0; + } + const x = class z { + static z = 2; + static { + let x = 3; + var y = x; + this.z = y; + } + create(){ return new z } + getZ(){ return this.z } + } + var t = new x; + console.log(y, x.z, z.z); +})() +|}; + [%expect + {| + $ cat "test.min.js" + 1: (function(){ + 2: var v3 = 0; + 3: class + 4: v4{static + 5: z + 6: = + 7: 0 + 8: } + 9: const + 10: v2 = + 11: class + 12: v5{static + 13: z + 14: = + 15: 2 + 16: static{let v6 = 3; var v7 = v6; this.z = v7;} + 17: create(){return new v5;} + 18: getZ(){return this.z;} + 19: }; + 20: var v1 = new v2; + 21: console.log(v3, v2.z, v4.z); + 22: } + 23: ()); + 0 3 0 |}] + let%expect_test "let inside block" = test {|