diff --git a/src/build/fan/BuildPod.fan b/src/build/fan/BuildPod.fan index 7e9a855c6..d563a15ec 100644 --- a/src/build/fan/BuildPod.fan +++ b/src/build/fan/BuildPod.fan @@ -227,7 +227,7 @@ abstract class BuildPod : BuildScript } } - @NoDoc protected CompilerInput stdFanCompilerInput() + @NoDoc protected virtual CompilerInput stdFanCompilerInput() { // add my own meta meta := this.meta.dup diff --git a/src/compiler/fan/CompilerInput.fan b/src/compiler/fan/CompilerInput.fan index d5e15bbf0..8266701b7 100644 --- a/src/compiler/fan/CompilerInput.fan +++ b/src/compiler/fan/CompilerInput.fan @@ -38,12 +38,18 @@ class CompilerInput ** Bool isScript := false - ** ** Flag indicating if we should treat all types as having the @Js facet ** Bool forceJs := false + + ** + ** Flag to force Fantom closures to be compiled with type reflection information + ** when emitting JavaScript. + ** + Bool jsReflectClosures := false + ** ** Version to include in ouput pod's manifest. ** diff --git a/src/compilerEs/fan/CompileEsPlugin.fan b/src/compilerEs/fan/CompileEsPlugin.fan index 1bb9055d1..f410e6ba5 100644 --- a/src/compilerEs/fan/CompileEsPlugin.fan +++ b/src/compilerEs/fan/CompileEsPlugin.fan @@ -23,6 +23,7 @@ class CompileEsPlugin : CompilerStep { this.sourcemap = SourceMap(this) this.js = JsWriter(buf.out, sourcemap) + this.closureSupport = JsClosure(this) pod.depends.each |depend| { dependOnNames[depend.name] = true } readJsProps } @@ -45,6 +46,8 @@ class CompileEsPlugin : CompilerStep [Str:Bool] dependOnNames := [:] { def = false } + JsClosure closureSupport { private set } + ////////////////////////////////////////////////////////////////////////// // js.props ////////////////////////////////////////////////////////////////////////// diff --git a/src/compilerEs/fan/ast/JsClosure.fan b/src/compilerEs/fan/ast/JsClosure.fan new file mode 100644 index 000000000..54e90ea6f --- /dev/null +++ b/src/compilerEs/fan/ast/JsClosure.fan @@ -0,0 +1,107 @@ +// +// Copyright (c) 2024, Brian Frank and Andy Frank +// Licensed under the Academic Free License version 3.0 +// +// History: +// 15 Mar 2024 Matthew Giannini Creation +// + +using compiler + +** +** JsClosure provides utilities for working with JS closures. +** +class JsClosure : JsNode +{ + new make(CompilerSupport s) : super(s) + { + } + + Bool emitReflection() { c.input.jsReflectClosures } + + ** Write the actual ClosureExpr + Void writeClosure(ClosureExpr ce) + { + + CType[] sigTypes := [,].addAll(ce.signature.params).add(ce.signature.ret) + isJs := sigTypes.all { !it.isForeign && checkJsSafety(it, loc) } + if (isJs) + { + if (emitReflection) + { + js.wl("sys.Func.__reflect(").indent + js.wl("${mapFuncSpec(ce)},", loc) + } + + js.w("${methodParams(ce.doCall.params)}", loc).wl(" => {") + js.indent + old := plugin.thisName + plugin.thisName = "this\$" + writeBlock(ce.doCall.code) + plugin.thisName = old + js.unindent + js.w("}") + + if (emitReflection) + { + js.w(")").unindent + } + } + else + { + // this closure uses non-JS types. Write a closure that documents this fact + js.wl("() => {") + js.wl(" // Cannot write closure. Signature uses non-JS types: ${ce.signature}") + js.wl(" throw sys.UnsupportedErr.make('Closure uses non-JS types: ' + ${ce.signature.toStr.toCode});") + js.w("}") + } + } + + ** Write the unique closure specification fields for this pod (JsPod) + override Void write() + { + varToFunc.each |MethodDef func, Str var| + { + loc := func.loc + js.w("const ${var} = [${qnameToJs(func.ret)}.type\$,") + js.w("sys.List.make(sys.Param.type\$, [") + func.params.each |p,i| + { + if (i>0) js.w(",") + js.w("new sys.Param(${p.name.toCode}, ${p.paramType.signature.toCode}, ${p.hasDefault})") + } + js.w("])") + .w("];").nl + } + js.nl + } + + private Str mapFuncSpec(ClosureExpr ce) + { + var := specKeyToVar.getOrAdd(specKey(ce)) |->Str| + { + // "${ce.enclosingType.pod}.__clos${plugin.nextUid}" + "__clos${plugin.nextUid}" + } + varToFunc[var] = ce.doCall + return var + } + + private static Str specKey(ClosureExpr ce) + { + MethodDef func := ce.doCall + buf := StrBuf() + func.params.each |p| + { + buf.add("${p.name}-${p.paramType.signature}-${p.hasDefault},") + } + buf.add("${func.ret.signature}") + return buf.toStr + } + + ** Func spec key to field variable name + private Str:Str specKeyToVar := [:] + + ** Func spec field variable name to prototype function (for params and return type) + private Str:MethodDef varToFunc := [:] { ordered = true } +} \ No newline at end of file diff --git a/src/compilerEs/fan/ast/JsExpr.fan b/src/compilerEs/fan/ast/JsExpr.fan index 53e268114..a1847ca84 100644 --- a/src/compilerEs/fan/ast/JsExpr.fan +++ b/src/compilerEs/fan/ast/JsExpr.fan @@ -461,27 +461,7 @@ class JsExpr : JsNode private Void writeClosure(ClosureExpr ce) { - CType[] sigTypes := [,].addAll(ce.signature.params).add(ce.signature.ret) - isJs := sigTypes.all { !it.isForeign && checkJsSafety(it, loc) } - if (isJs) - { - js.w("${methodParams(ce.doCall.params)}", loc).wl(" => {") - js.indent - old := plugin.thisName - plugin.thisName = "this\$" - writeBlock(ce.doCall.code) - plugin.thisName = old - js.unindent - js.w("}") - } - else - { - // this closure uses non-JS types. Write a closure that documents this fact - js.wl("() => {") - js.wl(" // Cannot write closure. Signature uses non-JS types: ${ce.signature}") - js.wl(" throw sys.UnsupportedErr.make('Closure uses non-JS types: ' + ${ce.signature.toStr.toCode});") - js.w("}") - } + plugin.closureSupport.writeClosure(ce) } ////////////////////////////////////////////////////////////////////////// diff --git a/src/compilerEs/fan/ast/JsPod.fan b/src/compilerEs/fan/ast/JsPod.fan index b3ac49228..debcbd5fc 100644 --- a/src/compilerEs/fan/ast/JsPod.fan +++ b/src/compilerEs/fan/ast/JsPod.fan @@ -47,6 +47,7 @@ class JsPod : JsNode writeTypes writeTypeInfo writeProps + writeClosureFields writeNatives writeExports js.wl("}).call(this);") @@ -248,6 +249,11 @@ class JsPod : JsNode } } + private Void writeClosureFields() + { + plugin.closureSupport.write + } + private Void writeNatives() { natives.each |f| { js.minify(f.in) } diff --git a/src/sys/es/fan/Func.js b/src/sys/es/fan/Func.js index cb3c4bf72..d469adc95 100644 --- a/src/sys/es/fan/Func.js +++ b/src/sys/es/fan/Func.js @@ -46,6 +46,8 @@ class Func extends Obj { static callList(f, args) { return f.__method.callList(args); } static params(f) { + if (f.__params) return f.__params; + let mparams = f.__method.params(); let fparams = mparams; if ((f.__method.flags$() & (FConst.Static | FConst.Ctor)) == 0) { @@ -67,6 +69,13 @@ class Func extends Obj { static toStr(f) { return "sys::Func"; } + static __reflect(spec, f) + { + f.__returns = spec[0]; + f.__params = spec[1]; + return f; + } + // TODO:FIXIT // retype(t) { // t = t.toNonNullable();