Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix nested inline fn expansion #34

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

camsaul
Copy link

@camsaul camsaul commented Aug 19, 2020

Fixes #33

The root cause of the issue was that the last form in expanded inline function calls would be marked with ^::transformed metadata to prevent infinite expansions in cases where the resultant form looked like it could be an inline function form. e.g. (inc 1) expands to (. clojure.lang.Numbers (inc 1)); the intention was that by marking the (inc 1) as ^::transformed we would know not to try to expand it as an inline function a second time. This however caused problems when using inlineable functions inside other inlineable functions -- see #33.

After poking around a bit I determined the ^::transformed metadata isn't needed because the code that handles java interop forms (dothandler) does not apply the expression-walking function to the method form (e.g. (inc 1)) or the parent . Java interop form. I added tests to verify that this indeed works as expected even without marking things as ^::transformed.

As part of figuring out how to fix this issue I did a bit of refactoring and broke macroexpand out into a series of smaller functions to make it easier to follow. I added a bunch of extra tests around this refactored code. Hope that's ok!

Comment on lines +1 to +3
((nil . ((indent-tabs-mode . nil)
(require-final-newline . t)))
(clojure-mode . ((cljr-favor-prefix-notation . nil))))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a bit of editor config settings for people who use Emacs

@camsaul camsaul force-pushed the fix-nested-inline-fn-expansion branch 2 times, most recently from 005d071 to 93c2875 Compare August 19, 2020 19:23
project.clj Outdated
Comment on lines 12 to 16
:profiles {:provided {:dependencies [[org.clojure/clojure "1.8.0"]]}}
:profiles {:dev {:dependencies [[org.clojure/clojure "1.10.1"]
[pjstadig/humane-test-output "0.10.0"]]
:injections [(require 'pjstadig.humane-test-output)
(pjstadig.humane-test-output/activate!)]}}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the :provided profile to a :dev one and added humane-test-output to make test failures easier to read

@camsaul camsaul force-pushed the fix-nested-inline-fn-expansion branch from 93c2875 to 1b81669 Compare August 19, 2020 19:30
Comment on lines +13 to +16
:dev {:dependencies [[org.clojure/clojure "1.10.1"]
[pjstadig/humane-test-output "0.10.0"]]
:injections [(require 'pjstadig.humane-test-output)
(pjstadig.humane-test-output/activate!)]}}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a :dev profile that uses Clojure 1.10.1 by default and adds humane-test-output to make the tests easier to read

Comment on lines -14 to +18
:javac-options ["-target" "1.6" "-source" "1.6"])
:javac-options ["-target" "1.7" "-source" "1.7"])
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using Java 14 locally and it doesn't support compiling to Java 1.6 bytecode. I think dropping 1.6 support makes sense -- Clojure 1.10+ requires Java 8+ and Clojure 1.5+ requires Java 7 for all features to work out-of-the-box

Comment on lines -209 to +316
'def def-handler
'fn* fn-handler
'let* let-handler
'loop* let-handler
'letfn* letfn-handler
'case* case-handler
'try try-handler
'catch catch-handler
'reify* reify-handler
'deftype* deftype-handler
'. dot-handler
#(doall (map %1 %2)))
walk-exprs' (special-meta x)))

(instance? java.util.Map$Entry x)
(clojure.lang.MapEntry.
(walk-exprs' (key x))
(walk-exprs' (val x)))

(or
(set? x)
(vector? x))
(into (empty x) (map walk-exprs' x))

(instance? clojure.lang.IRecord x)
x

(map? x)
(into (empty x) (map walk-exprs' x))

;; special case to handle clojure.test
(and (symbol? x) (-> x meta :test))
(vary-meta x update-in [:test] walk-exprs')

:else
x)]
(if (instance? clojure.lang.IObj x')
(with-meta x' (merge (meta x) (meta x')))
x')))))
(cmp/with-base-env
(let [x (try
(macroexpand x special-form?)
(catch ClassNotFoundException _
x))
walk-exprs' (partial walk-exprs predicate handler special-form?)
x' (cond

(and (head= x 'var) (predicate x))
(handler (eval x))

(and (head= x 'quote) (not (predicate x)))
x

(predicate x)
(handler x)

(seq? x)
(if (or (and (not try-clause?)
(#{'catch 'finally} (first x)))
(not (contains? special-forms (first x))))
(doall (map walk-exprs' x))
((condp = (first x)
'do do-handler
'def def-handler
'fn* fn-handler
'let* let-handler
'loop* let-handler
'letfn* letfn-handler
'case* case-handler
'try try-handler
'catch catch-handler
'reify* reify-handler
'deftype* deftype-handler
'. dot-handler
#(doall (map %1 %2)))
walk-exprs' (special-meta x)))

(instance? java.util.Map$Entry x)
(clojure.lang.MapEntry.
(walk-exprs' (key x))
(walk-exprs' (val x)))

(or
(set? x)
(vector? x))
(into (empty x) (map walk-exprs' x))

(instance? clojure.lang.IRecord x)
x

(map? x)
(into (empty x) (map walk-exprs' x))

;; special case to handle clojure.test
(and (symbol? x) (-> x meta :test))
(vary-meta x update-in [:test] walk-exprs')

:else
x)]
(merge-meta x' (meta x))))))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only change here was to fix the indentation and use the merge-meta helper function I wrote to simplify things a bit

@camsaul
Copy link
Author

camsaul commented Feb 18, 2022

@ztellman bump

@ztellman
Copy link
Owner

Hi, I'm sorry for my lack of response so far, this isn't a repo I've thought about in quite some time. If it were to get moved over to the clj-commons org, would you be interested in maintaining it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Wrong expansion of inlined forms inside other inlined forms
2 participants