From 105d7fcf754489b7ff8c313819c85d79086d5b6d Mon Sep 17 00:00:00 2001 From: playX Date: Sun, 5 Dec 2021 14:33:53 +0300 Subject: [PATCH] js: support JS.await (#12726) --- cmd/tools/vtest.v | 3 +++ vlib/v/ast/ast.v | 3 ++- vlib/v/checker/checker.v | 29 ++++++++++++++++++++++++++ vlib/v/gen/js/fn.v | 44 ++++++++++++++++++++++++++-------------- vlib/v/gen/js/js.v | 5 +++-- 5 files changed, 66 insertions(+), 18 deletions(-) diff --git a/cmd/tools/vtest.v b/cmd/tools/vtest.v index be551a2457..8829f67fef 100644 --- a/cmd/tools/vtest.v +++ b/cmd/tools/vtest.v @@ -114,6 +114,9 @@ fn should_test(path string, backend string) ShouldTestStatus { if path.ends_with('_test.v') { return .test } + if path.ends_with('_test.js.v') { + return .test + } if path.ends_with('.v') && path.count('.') == 2 { if !path.all_before_last('.v').all_before_last('.').ends_with('_test') { return .ignore diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 6b78086dc5..6285711aa6 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -485,8 +485,9 @@ pub mut: return_type_pos token.Position // `string` in `fn (u User) name() string` position has_return bool should_be_skipped bool + has_await bool // 'true' if this function uses JS.await // - comments []Comment // comments *after* the header, but *before* `{`; used for InterfaceDecl + comments []Comment // comments *after* the header, but *before* `{`; used for InterfaceDecl next_comments []Comment // coments that are one line after the decl; used for InterfaceDecl // source_file &File = 0 diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 95148f7dde..ddd26ebd9d 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2550,6 +2550,35 @@ pub fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) c.need_recheck_generic_fns = true } } + if fn_name == 'JS.await' { + if node.args.len > 1 { + c.error('JS.await expects 1 argument, a promise value (e.g `JS.await(fs.read())`', + node.pos) + return ast.void_type + } + + typ := c.expr(node.args[0].expr) + tsym := c.table.get_type_symbol(typ) + + if !tsym.name.starts_with('js.promise.Promise<') { + c.error('JS.await: first argument must be a promise, got `$tsym.name`', node.pos) + return ast.void_type + } + c.table.cur_fn.has_await = true + match tsym.info { + ast.Struct { + mut ret_type := tsym.info.concrete_types[0] + ret_type = ret_type.set_flag(.optional) + node.return_type = ret_type + return ret_type + } + else { + c.error('JS.await: Promise must be a struct type', node.pos) + return ast.void_type + } + } + panic('unreachable') + } if fn_name == 'json.encode' { } else if fn_name == 'json.decode' && node.args.len > 0 { if node.args.len != 2 { diff --git a/vlib/v/gen/js/fn.v b/vlib/v/gen/js/fn.v index aba03a9dc1..084bc0b522 100644 --- a/vlib/v/gen/js/fn.v +++ b/vlib/v/gen/js/fn.v @@ -66,23 +66,35 @@ fn (mut g JsGen) js_call(node ast.CallExpr) { g.call_stack << node it := node call_return_is_optional := it.return_type.has_flag(.optional) + is_await := node.name == 'JS.await' if call_return_is_optional { - g.writeln('(function () {') + if is_await { + g.writeln('await (async function () {') + } else { + g.writeln('(function () {') + } g.writeln('try {') g.writeln('let tmp = ') } if it.is_ctor_new { g.write('new ') } - g.write('${g.js_mname(it.name)}(') - for i, arg in it.args { - g.expr(arg.expr) - if i != it.args.len - 1 { - g.write(', ') + if is_await { + g.write('await (') + + g.expr(it.args[0].expr) + g.write(').promise') + } else { + g.write('${g.js_mname(it.name)}(') + for i, arg in it.args { + g.expr(arg.expr) + if i != it.args.len - 1 { + g.write(', ') + } } + // end call + g.write(')') } - // end call - g.write(')') if call_return_is_optional { g.write(';\n') g.writeln('if (tmp === null) throw "none";') @@ -588,18 +600,20 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl, typ FnGenType) { g.writeln('${g.typ(it.receiver.typ)}.prototype.$it.name = ') } - has_go := fn_has_go(it) - + mut has_go := fn_has_go(it) || it.has_await + for attr in it.attrs { + if attr.name == 'async' { + has_go = true + break + } + } is_main := it.name == 'main.main' g.gen_attrs(it.attrs) if is_main { // there is no concept of main in JS but we do have iife g.writeln('/* program entry point */') - - // g.write('(') - if has_go { - g.write('async ') - } + // main function is always async + g.write('async ') g.write('function js_main(') } else if it.is_anon { g.write('function (') diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index b6b4306ae0..557a3125c7 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -321,6 +321,7 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { if g.pref.sourcemap { out += g.create_sourcemap() } + return out } @@ -335,7 +336,7 @@ fn (g JsGen) create_sourcemap() string { pub fn (mut g JsGen) gen_js_main_for_tests() { g.enter_namespace('main') - g.writeln('function js_main() { ') + g.writeln('async function js_main() { ') g.inc_indent() all_tfuncs := g.get_all_test_function_names() @@ -351,7 +352,7 @@ pub fn (mut g JsGen) gen_js_main_for_tests() { g.writeln('main__BenchedTests_testing_step_start(bt,new string("$tcname"))') } - g.writeln('try { ${tcname}(); } catch (_e) {} ') + g.writeln('try { let res = ${tcname}(); if (res instanceof Promise) { await res; } } catch (_e) {} ') if g.pref.is_stats { g.writeln('main__BenchedTests_testing_step_end(bt);') }