Skip to content

Commit

Permalink
build,compilerEs: support generating closure reflection info in js
Browse files Browse the repository at this point in the history
  • Loading branch information
Matthew Giannini authored and Matthew Giannini committed Mar 15, 2024
1 parent 5db784e commit 33465b5
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/build/fan/BuildPod.fan
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion src/compiler/fan/CompilerInput.fan
Original file line number Diff line number Diff line change
Expand Up @@ -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.
**
Expand Down
3 changes: 3 additions & 0 deletions src/compilerEs/fan/CompileEsPlugin.fan
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -45,6 +46,8 @@ class CompileEsPlugin : CompilerStep

[Str:Bool] dependOnNames := [:] { def = false }

JsClosure closureSupport { private set }

//////////////////////////////////////////////////////////////////////////
// js.props
//////////////////////////////////////////////////////////////////////////
Expand Down
107 changes: 107 additions & 0 deletions src/compilerEs/fan/ast/JsClosure.fan
Original file line number Diff line number Diff line change
@@ -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 }
}
22 changes: 1 addition & 21 deletions src/compilerEs/fan/ast/JsExpr.fan
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

//////////////////////////////////////////////////////////////////////////
Expand Down
6 changes: 6 additions & 0 deletions src/compilerEs/fan/ast/JsPod.fan
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class JsPod : JsNode
writeTypes
writeTypeInfo
writeProps
writeClosureFields
writeNatives
writeExports
js.wl("}).call(this);")
Expand Down Expand Up @@ -248,6 +249,11 @@ class JsPod : JsNode
}
}

private Void writeClosureFields()
{
plugin.closureSupport.write
}

private Void writeNatives()
{
natives.each |f| { js.minify(f.in) }
Expand Down
9 changes: 9 additions & 0 deletions src/sys/es/fan/Func.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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();
Expand Down

0 comments on commit 33465b5

Please sign in to comment.