diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87be841..f0be025 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,8 +15,10 @@ jobs: haxe-version: - stable - nightly - target: + target: - interp + - interp -D function_sugar + - interp -D deprecated_function_sugar - neko - node - python @@ -26,7 +28,7 @@ jobs: steps: - name: Check out repo uses: actions/checkout@v2 - + - name: Get yarn cache directory path id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" @@ -38,22 +40,22 @@ jobs: key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn- - + - name: Cache Haxe uses: actions/cache@v1 with: path: ${{ startsWith(runner.os, 'windows') && '%AppData%' || '~/haxe' }} key: ${{ runner.os }}-haxe - + - name: Install Lix uses: lix-pm/setup-lix@master - + - name: Install Haxe run: lix install haxe ${{ matrix.haxe-version }} - + - name: Install Haxe Libraries run: lix download - + - name: Run Test run: lix run travix ${{ matrix.target }} @@ -61,11 +63,11 @@ jobs: runs-on: ubuntu-latest needs: test if: startsWith(github.ref, 'refs/tags/') # consider using the "release" event. see: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#release - + steps: - name: Check out repo uses: actions/checkout@v2 - + - name: Get yarn cache directory path id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" @@ -77,24 +79,23 @@ jobs: key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn- - + - name: Cache Haxe uses: actions/cache@v1 with: path: ${{ startsWith(runner.os, 'windows') && '%AppData%' || '~/haxe' }} key: ${{ runner.os }}-haxe - + - name: Install Lix uses: lix-pm/setup-lix@master - + - name: Install Haxe run: lix install haxe stable - + - name: Install Haxe Libraries run: lix download - + - name: Release to Haxelib run: lix run travix release env: HAXELIB_AUTH: ${{ secrets.HAXELIB_AUTH }} - \ No newline at end of file diff --git a/.haxerc b/.haxerc index f8137ff..ef43126 100644 --- a/.haxerc +++ b/.haxerc @@ -1,4 +1,4 @@ { - "version": "4.2.0", + "version": "4.3.6", "resolveLibs": "scoped" } \ No newline at end of file diff --git a/README.md b/README.md index 82d578e..b84099d 100644 --- a/README.md +++ b/README.md @@ -503,7 +503,7 @@ hxx(' ## Implicit function syntax -For attributes that are functions with 0 or 1 argument, you may write the function body directly: +With `using tink.hxx.FunctionSugar;` (which can be placed in [`import.hx`](https://haxe.org/manual/type-system-import-defaults.html)), for attributes that are functions with 0 or 1 argument, you may write the function body directly: ```haxe hxx(' @@ -514,6 +514,14 @@ hxx(' Note that if there's exactly one argument, it will be called "event". +Up until version 0.25 this has been a standard feature, but it has been relegated to an opt-in (via `using`) for a few reasons: + +1. some users - especially new comers - consider it confusing rather than convenient (that is left for you to judge) +2. it's hard to implement in a way that works well with IDE services (this may change in the future though) +3. it makes code harder to port to JSX, should the need arise + +If you have exisiting code relying on this feature but wish to migrate away from it, you may use `using tink.hxx.DeprecatedFunctionSyntax;` instead, which will also activate the feature, but produces warnings anywhere it is used. + ## Whitespace The treatment of whitespace depends on whether the generated structure even has any notion of whitespace or not. The default HXX flavor uses the JSX style whitespace explained below. @@ -548,7 +556,7 @@ Example: ``` -The first version will retain the white space between the two spans, the second one will not. +The first version will retain the white space between the two spans, the second one will not. To enforce a whitspace, `${' '}` can be used, e.g.: diff --git a/haxe_libraries/tink_anon.hxml b/haxe_libraries/tink_anon.hxml index e885a54..6046e27 100644 --- a/haxe_libraries/tink_anon.hxml +++ b/haxe_libraries/tink_anon.hxml @@ -1,4 +1,4 @@ -# @install: lix --silent download "gh://github.com/haxetink/tink_anon#ef441a73a41f9ae4ca0f060035ff2c188618df4c" into tink_anon/0.7.0/github/ef441a73a41f9ae4ca0f060035ff2c188618df4c +# @install: lix --silent download "gh://github.com/haxetink/tink_anon#0277e6e3f97a7878f1aa9aeeccc4b7be0e9c82bc" into tink_anon/0.7.0/github/0277e6e3f97a7878f1aa9aeeccc4b7be0e9c82bc -lib tink_macro --cp ${HAXE_LIBCACHE}/tink_anon/0.7.0/github/ef441a73a41f9ae4ca0f060035ff2c188618df4c/src +-cp ${HAXE_LIBCACHE}/tink_anon/0.7.0/github/0277e6e3f97a7878f1aa9aeeccc4b7be0e9c82bc/src -D tink_anon=0.7.0 \ No newline at end of file diff --git a/haxe_libraries/tink_core.hxml b/haxe_libraries/tink_core.hxml index 43cf59b..37d612f 100644 --- a/haxe_libraries/tink_core.hxml +++ b/haxe_libraries/tink_core.hxml @@ -1,3 +1,3 @@ -# @install: lix --silent download "gh://github.com/haxetink/tink_core#d74b0e03d7f9efcf2c2b73fecd99d240dd6d57ff" into tink_core/2.0.2/github/d74b0e03d7f9efcf2c2b73fecd99d240dd6d57ff --cp ${HAXE_LIBCACHE}/tink_core/2.0.2/github/d74b0e03d7f9efcf2c2b73fecd99d240dd6d57ff/src +# @install: lix --silent download "gh://github.com/haxetink/tink_core#f500203b657859bfde36c49e54f95a0b1fc2b165" into tink_core/2.0.2/github/f500203b657859bfde36c49e54f95a0b1fc2b165 +-cp ${HAXE_LIBCACHE}/tink_core/2.0.2/github/f500203b657859bfde36c49e54f95a0b1fc2b165/src -D tink_core=2.0.2 \ No newline at end of file diff --git a/src/tink/hxx/DeprecatedFunctionSugar.hx b/src/tink/hxx/DeprecatedFunctionSugar.hx new file mode 100644 index 0000000..c961e82 --- /dev/null +++ b/src/tink/hxx/DeprecatedFunctionSugar.hx @@ -0,0 +1,3 @@ +package tink.hxx; + +extern class DeprecatedFunctionSugar {} \ No newline at end of file diff --git a/src/tink/hxx/Expression.hx b/src/tink/hxx/Expression.hx new file mode 100644 index 0000000..8854126 --- /dev/null +++ b/src/tink/hxx/Expression.hx @@ -0,0 +1,5 @@ +package tink.hxx; + +@:callable @:transitive +abstract Expression(()->T) from ()->T to ()->T { +} \ No newline at end of file diff --git a/src/tink/hxx/FunctionSugar.hx b/src/tink/hxx/FunctionSugar.hx new file mode 100644 index 0000000..a4a8662 --- /dev/null +++ b/src/tink/hxx/FunctionSugar.hx @@ -0,0 +1,3 @@ +package tink.hxx; + +extern class FunctionSugar {} \ No newline at end of file diff --git a/src/tink/hxx/Generator.hx b/src/tink/hxx/Generator.hx index 6199570..6730527 100644 --- a/src/tink/hxx/Generator.hx +++ b/src/tink/hxx/Generator.hx @@ -19,14 +19,7 @@ class Generator { dynamic function adjustFormattingPos(pos:Position) return pos; - public var defaults(default, null):LazyTag>>>; - - public function new(?defaults) { - this.defaults = switch defaults { - case null: []; - case v: v; - } - + public function new() { var shift = { var pos = (macro null).pos; Context.getPosInfos(('foo'.formatString(pos):Expr).pos).min - Context.getPosInfos(pos).min; @@ -51,9 +44,10 @@ class Generator { } function tag(n:Node, tag:Tag, pos:Position) { + var paramatrized = tag.parametrized(); var aliases = tag.args.aliases, - children = tag.args.children, + children:Type = paramatrized.children,//tag.args.children, fields = tag.args.fields, fieldsType = tag.args.fieldsType, childrenAttribute = tag.args.childrenAttribute; @@ -129,7 +123,7 @@ class Generator { case l: function get() - return applyCustomRules(tag.args.children, this.childList.bind(l, _)); + return applyCustomRules(children, this.childList.bind(l, _)); switch childrenAttribute { case null: @@ -151,7 +145,6 @@ class Generator { args.unshift( compute(function () { - var paramatrized = tag.parametrized(); var attrType = paramatrized.fieldsType; return mergeParts( @@ -178,6 +171,13 @@ class Generator { }, duplicateField: function (name) return 'duplicate attribute $name', missingField: function (f) return 'missing attribute ${f.name}',//TODO: might be nice to put type here + }, + (owner, field) -> { + var e = owner.field(field.name, owner.pos); + switch field.type.get() { + case None: e; + case Some(t): applyCustomRules(t, _ -> e); + } } ); }) @@ -189,8 +189,8 @@ class Generator { return invoke({ value: tag.realPath, pos: tagName.pos }, tag.create, args, tagName.pos); } - function later(fn:Void->Expr) - return withTags.bind(localTags, fn).bounce(); + inline function later(fn:Void->Expr) + return fn.bounce(); function makePart(name:StringAt, value:Expr, ?quotes):Part return { @@ -213,16 +213,14 @@ class Generator { } }; - function applyCustomRules(t, getValue:Type->Expr) { - var transform = getTransform(t); - t = transform.reduced.or(t); - var e = getValue(t); - return switch transform.postprocessor { - case PTyped(f): f.bind(e).bounce(); - case PUntyped(f): f(e); - default: e; + function applyCustomRules(t:Type, getValue:Type->Expr) + return switch t.reduce() { + case TAbstract(_.toString() => 'tink.hxx.Expression', [t]): + var ret = applyCustomRules(t, getValue); + ret = macro @:pos(ret.pos) function () return $ret; + default: + getTransform(t)(getValue(t)); } - } function invoke(name:StringAt, create:TagCreate, args:Array, pos:Position) return @@ -251,7 +249,6 @@ class Generator { return !Context.unify(Context.getType('Array'), t.reduce()); function complexAttribute(n:Node):Part { - var localTags = localTags; return { name: n.name.value, pos: n.name.pos, @@ -298,32 +295,11 @@ class Generator { return this.childList(n.children, ret); var body = - if (splat) { - var tags = switch requiredArgs[0].t.getFields() { - case Success(fields): - var ret = - #if haxe4 - localTags.copy(); - #else - [for (t in localTags.keys()) t => localTags[t]]; - #end - - for (c in fields) - ret[c.name] = Tag.declaration.bind(c.name, _, c.type, c.params); - - ret; - default: - localTags; - } - + if (splat) macro @:pos(n.name.pos) { tink.Anon.splat(__data__); - return ${ - if (tags != localTags) withTags(tags, getBody) - else later(withTags.bind(tags, getBody)) - }; + return ${later(getBody)}; } - } else getBody(); body.func(args).asExpr(); default: @@ -352,6 +328,7 @@ class Generator { throw 'assert'; } } + else if (c == null || c.value.length == 0) macro ([]:$ct); else macro { var __r = []; if (false) (__r:$ct); @@ -501,42 +478,30 @@ class Generator { } function node(n, pos) - return tag(n, getTag(n.name), pos); - - function getTag(name:StringAt):Tag - return Tag.resolve(localTags, name).sure(); - - var localTags:MapTag>; + return tag(n, Tag.resolve(n.name), pos); var postProcess:Expr->Expr = function (e) return e; - function withTags(tags, f:Void->T) { - #if tink_syntaxhub var lastFn = postProcess; #end - - var last = localTags; - return tink.core.Error.tryFinally( - function () { - localTags = tags; - #if tink_syntaxhub - postProcess = switch tink.SyntaxHub.exprLevel.appliedTo(new ClassBuilder()) { - case Some(f): f; - case None: function (e) return e; - } - #end - return f(); - }, - function () { - localTags = last; - #if tink_syntaxhub postProcess = lastFn; #end - } - ); - } - public function createContext():GeneratorContext { - var tags = Tag.getAllInScope(defaults); return { - isVoid: function (name) return Tag.resolve(tags, name).match(Success({ isVoid: true })), - generateRoot: function (root:Children) return withTags(tags, function () return children(root)), + isVoid: function (name) return false, + generateRoot: function (root:Children) { + #if tink_syntaxhub + var lastFn = postProcess; + return tink.core.Error.tryFinally( + function () { + postProcess = switch tink.SyntaxHub.exprLevel.appliedTo(new ClassBuilder()) { + case Some(f): f; + case None: function (e) return e; + } + return children(root); + }, + function () postProcess = lastFn + ); + #else + return children(root); + #end + } } } diff --git a/src/tink/hxx/Helpers.hx b/src/tink/hxx/Helpers.hx index 1b0f83d..4e4c7ed 100644 --- a/src/tink/hxx/Helpers.hx +++ b/src/tink/hxx/Helpers.hx @@ -125,8 +125,25 @@ class Helpers { return s.substring(pos, max); } + static var measured = false; static public function functionSugar(value:Expr, t:Type) { + var mode = Off; + + for (c in Context.getLocalUsing()) // TODO: doing this here might be rather expensive + switch c.toString() { + case 'tink.hxx.FunctionSugar': + mode = On; + break; + case 'tink.hxx.DeprecatedFunctionSugar': + mode = Deprecated; + break; + default: + } + + if (mode == Off) + return value; + while (true) switch value { case macro ($v): value = v; default: break; @@ -146,6 +163,9 @@ class Helpers { case TDynamic(null): value.reject('Cannot use `Dynamic` as callback'); case found: + if (mode == Deprecated) + e.pos.warning('Automatic function wrapping is deprecated'); + if (Context.unify(found, t)) Context.storeTypedExpr(f.expr); else if (typed.hasThis()) macro @:pos(e.pos) (function () return $e)(); else Context.storeTypedExpr(typed); @@ -189,9 +209,9 @@ class Helpers { } } - static var transformers = new Map>(); + static var transformers = new MapExpr>>(); - static function getCustomTransformer(r:haxe.macro.Type.Ref):Option { + static function getCustomTransformer(r:haxe.macro.Type.Ref) { var id = r.toString(); return switch transformers[id] { case null: @@ -199,31 +219,19 @@ class Helpers { case []: None; case [{ params: params }]: - var basicType = null, - transform = null; + var transform = null; for (p in params) switch p { - case macro basicType = $e: basicType = e; case macro transform = $e: transform = e; case macro $e = $_: e.reject('unknown option ${e.toString()}'); default: p.reject('should be ` =