[CLJ-701] Primitive return type of loop and try is lost Created: 03/Jan/11 Updated: 08/Oct/18 |
|
Status: | Open |
Project: | Clojure |
Component/s: | None |
Affects Version/s: | Backlog |
Fix Version/s: | Release 1.11 |
Type: | Defect | Priority: | Major |
Reporter: | Chouser | Assignee: | Unassigned |
Resolution: | Unresolved | Votes: | 7 |
Labels: | None | ||
Environment: |
Clojure commit 9052ca1854b7b6202dba21fe2a45183a4534c501, version 1.3.0-master-SNAPSHOT |
Attachments: |
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
Patch: | Code and Test |
Approval: | Incomplete |
Description |
(set! *warn-on-reflection* true)
(fn [] (loop [b 0] (recur (loop [a 1] a))))
Generates the following warnings: recur arg for primitive local: b is not matching primitive, had: Object, needed: long Auto-boxing loop arg: b This is interesting for several reasons. For one, if the arg to recur is a let form, there is no warning: (fn [] (loop [b 0] (recur (let [a 1] a)))) Also, the compiler appears to understand the return type of loop forms just fine: (use '[clojure.contrib.repl-utils :only [expression-info]]) (expression-info '(loop [a 1] a)) ;=> {:class long, :primitive? true} The problem can of course be worked around using an explicit cast on the loop form: (fn [] (loop [b 0] (recur (long (loop [a 1] a)))))
Reported by leafw in IRC: http://clojure-log.n01se.net/date/2011-01-03.html#10:31 See Also: CLJ-1422 Patch: 0001-CLJ-701-add-HoistedMethod-to-the-compiler-for-hoisti-v2.patch |
Comments |
Comment by a_strange_guy [ 03/Jan/11 4:36 PM ] |
The problem is that a 'loop form gets converted into an anonymous fn that gets called immediately, when the loop is in a expression context (eg. its return value is needed, but not as the return value of a method/fn). so (fn [] (loop [b 0] (recur (loop [a 1] a)))) gets converted into (fn [] (loop [b 0] (recur ((fn [] (loop [a 1] a)))))) see the code in the compiler: this conversion already bites you if you have mutable fields in a deftype and want to 'set! them in a loop |
Comment by Christophe Grand [ 23/Nov/12 2:28 AM ] |
loops in expression context are lifted into fns because else Hotspot doesn't optimize them.
Adressing all those problems isn't easy. [1] beware of |
Comment by Alex Miller [ 21/Oct/13 10:28 PM ] |
I don't think this is going to make it into 1.6, so removing the 1.6 tag. |
Comment by Kevin Downey [ 21/Jul/14 7:14 PM ] |
an immediate solution to this might be to hoist loops out in to distinct non-ifn types generated by the compiler with an invoke method that is typed to return the getJavaClass() type of the expression, that would give us the simplifying benefits of hoisting the code out and free use from the Object semantics of ifn |
Comment by Kevin Downey [ 22/Jul/14 8:39 PM ] |
I have attached a 3 part patch as hoistedmethod-pass-1.diff 3ed6fed8 adds a new ObjMethod type to represent expressions hoisted out in to their own methods on the enclosing class 9c39cac1 uses HoistedMethod to compile loops not in the return context 901e4505 hoists out try expressions and makes it possible for try to return a primitive expression (this might belong on http://dev.clojure.org/jira/browse/CLJ-1422) |
Comment by Kevin Downey [ 22/Jul/14 8:54 PM ] |
with hoistedmethod-pass-1.diff the example code generates bytecode like this user=> (println (no.disassemble/disassemble (fn [] (loop [b 0] (recur (loop [a 1] a)))))) // Compiled from form-init1272682692522767658.clj (version 1.5 : 49.0, super bit) public final class user$eval1675$fn__1676 extends clojure.lang.AFunction { // Field descriptor #7 Ljava/lang/Object; public static final java.lang.Object const__0; // Field descriptor #7 Ljava/lang/Object; public static final java.lang.Object const__1; // Method descriptor #10 ()V // Stack: 2, Locals: 0 public static {}; 0 lconst_0 1 invokestatic java.lang.Long.valueOf(long) : java.lang.Long [16] 4 putstatic user$eval1675$fn__1676.const__0 : java.lang.Object [18] 7 lconst_1 8 invokestatic java.lang.Long.valueOf(long) : java.lang.Long [16] 11 putstatic user$eval1675$fn__1676.const__1 : java.lang.Object [20] 14 return Line numbers: [pc: 0, line: 1] // Method descriptor #10 ()V // Stack: 1, Locals: 1 public user$eval1675$fn__1676(); 0 aload_0 [this] 1 invokespecial clojure.lang.AFunction() [23] 4 return Line numbers: [pc: 0, line: 1] // Method descriptor #25 ()Ljava/lang/Object; // Stack: 3, Locals: 3 public java.lang.Object invoke(); 0 lconst_0 1 lstore_1 [b] 2 aload_0 [this] 3 lload_1 [b] 4 invokevirtual user$eval1675$fn__1676.__hoisted1677(long) : long [29] 7 lstore_1 [b] 8 goto 2 11 areturn Line numbers: [pc: 0, line: 1] Local variable table: [pc: 2, pc: 11] local: b index: 1 type: long [pc: 0, pc: 11] local: this index: 0 type: java.lang.Object // Method descriptor #27 (J)J // Stack: 2, Locals: 5 public long __hoisted1677(long b); 0 lconst_1 1 lstore_3 [a] 2 lload_3 3 lreturn Line numbers: [pc: 0, line: 1] Local variable table: [pc: 2, pc: 3] local: a index: 3 type: long [pc: 0, pc: 3] local: this index: 0 type: java.lang.Object [pc: 0, pc: 3] local: b index: 1 type: java.lang.Object } nil user=> the body of the method __hoisted1677 is the inner loop for reference the part of the bytecode from the same function compiled with 1.6.0 is pasted here https://gist.github.com/hiredman/f178a690718bde773ba0 the inner loop body is missing because it is implemented as its own IFn class that is instantiated and immediately executed. it closes over a boxed version of the numbers and returns an boxed version |
Comment by Kevin Downey [ 23/Jul/14 12:43 AM ] |
hoistedmethod-pass-2.diff replaces 901e4505 with f0a405e3 which fixes the implementation of MaybePrimitiveExpr for TryExpr with hoistedmethod-pass-2.diff the largest clojure project I have quick access to (53kloc) compiles and all the tests pass |
Comment by Alex Miller [ 23/Jul/14 12:03 PM ] |
Thanks for the work on this! |
Comment by Kevin Downey [ 23/Jul/14 2:05 PM ] |
I have been working through running the tests for all the contribs projects with hoistedmethod-pass-2.diff, there are some bytecode verification errors compiling data.json and other errors elsewhere, so there is still work to do |
Comment by Kevin Downey [ 25/Jul/14 7:08 PM ] |
hoistedmethod-pass-3.diff 49782161 * add HoistedMethod to the compiler for hoisting expresssions out well typed methods all contribs whose tests pass with master pass with this patch. the change from hoistedmethod-pass-2.diff in this patch is the addition of some bookkeeping for arguments that take up more than one slot |
Comment by Nicola Mometto [ 26/Jul/14 1:37 AM ] |
Kevin there's still a bug regarding long/doubles handling: Test case: user=> (fn [] (try 1 (finally)) 2) VerifyError (class: user$eval1$fn__2, method: invoke signature: ()Ljava/lang/Object;) Attempt to split long or double on the stack user/eval1 (NO_SOURCE_FILE:1) |
Comment by Kevin Downey [ 26/Jul/14 1:46 AM ] |
bah, all that work to figure out the thing I couldn't get right and of course I overlooked the thing I knew at the beginning. I want to get rid of some of the code duplication between emit and emitUnboxed for TryExpr, so when I get that done I'll fix the pop too |
Comment by Kevin Downey [ 26/Jul/14 12:52 PM ] |
hoistedmethod-pass-4.diff logically has the same three commits, but fixes the pop vs pop2 issue and rewrites emit and emitUnboxed for TryExpr to share most of their code |
Comment by Kevin Downey [ 26/Jul/14 12:58 PM ] |
hoistedmethod-pass-5.diff fixes a stupid mistake in the tests in hoistedmethod-pass-4.diff |
Comment by Nicola Mometto [ 21/Aug/15 1:03 PM ] |
|
Comment by Michael Blume [ 21/Aug/15 3:06 PM ] |
A naive attempt to bring the patch up to date results in compile-clojure: [java] Exception in thread "main" java.lang.ExceptionInInitializerError [java] at clojure.lang.Compile.<clinit>(Compile.java:29) [java] Caused by: java.lang.ClassCastException: clojure.lang.Compiler$HoistedMethod cannot be cast to clojure.lang.Compiler$FnMethod, compiling:(clojure/core.clj:439:11) [java] at clojure.lang.Compiler.analyzeSeq(Compiler.java:7360) [java] at clojure.lang.Compiler.analyze(Compiler.java:7154) [java] at clojure.lang.Compiler.analyzeSeq(Compiler.java:7341) [java] at clojure.lang.Compiler.analyze(Compiler.java:7154) [java] at clojure.lang.Compiler.access$300(Compiler.java:38) [java] at clojure.lang.Compiler$DefExpr$Parser.parse(Compiler.java:589) [java] at clojure.lang.Compiler.analyzeSeq(Compiler.java:7353) [java] at clojure.lang.Compiler.analyze(Compiler.java:7154) [java] at clojure.lang.Compiler.analyze(Compiler.java:7112) [java] at clojure.lang.Compiler.eval(Compiler.java:7416) [java] at clojure.lang.Compiler.load(Compiler.java:7859) [java] at clojure.lang.RT.loadResourceScript(RT.java:372) [java] at clojure.lang.RT.loadResourceScript(RT.java:363) [java] at clojure.lang.RT.load(RT.java:453) [java] at clojure.lang.RT.load(RT.java:419) [java] at clojure.lang.RT.doInit(RT.java:461) [java] at clojure.lang.RT.<clinit>(RT.java:331) [java] ... 1 more [java] Caused by: java.lang.ClassCastException: clojure.lang.Compiler$HoistedMethod cannot be cast to clojure.lang.Compiler$FnMethod [java] at clojure.lang.Compiler$FnExpr.parse(Compiler.java:4466) [java] at clojure.lang.Compiler.analyzeSeq(Compiler.java:7351) [java] ... 17 more |
Comment by Kevin Downey [ 25/Aug/15 1:00 PM ] |
the pass 6 patch builds cleanly on 1d5237f9d7db0bc5f6e929330108d016ac7bf76c(HEAD of master as of this moment) and runs clojure's tests. I have not verified it against other projects as I did with the previous patches (I don't remember how I did that) |
Comment by Michael Blume [ 25/Aug/15 1:38 PM ] |
I get the same error I shared before applying the new patch to 1d5237f |
Comment by Alex Miller [ 25/Aug/15 1:56 PM ] |
I get some whitespace warnings with hoistedmethod-pass-6.diff but the patch applies. On a compile I get: compile-clojure: [java] Exception in thread "main" java.lang.ExceptionInInitializerError [java] at clojure.lang.Compile.<clinit>(Compile.java:29) [java] Caused by: java.lang.ClassCastException: clojure.lang.Compiler$HoistedMethod cannot be cast to clojure.lang.Compiler$FnMethod, compiling:(clojure/core.clj:439:11) [java] at clojure.lang.Compiler.analyzeSeq(Compiler.java:7362) [java] at clojure.lang.Compiler.analyze(Compiler.java:7156) [java] at clojure.lang.Compiler.analyzeSeq(Compiler.java:7343) [java] at clojure.lang.Compiler.analyze(Compiler.java:7156) [java] at clojure.lang.Compiler.access$300(Compiler.java:38) [java] at clojure.lang.Compiler$DefExpr$Parser.parse(Compiler.java:588) [java] at clojure.lang.Compiler.analyzeSeq(Compiler.java:7355) [java] at clojure.lang.Compiler.analyze(Compiler.java:7156) [java] at clojure.lang.Compiler.analyze(Compiler.java:7114) [java] at clojure.lang.Compiler.eval(Compiler.java:7418) [java] at clojure.lang.Compiler.load(Compiler.java:7861) [java] at clojure.lang.RT.loadResourceScript(RT.java:372) [java] at clojure.lang.RT.loadResourceScript(RT.java:363) [java] at clojure.lang.RT.load(RT.java:453) [java] at clojure.lang.RT.load(RT.java:419) [java] at clojure.lang.RT.doInit(RT.java:461) [java] at clojure.lang.RT.<clinit>(RT.java:331) [java] ... 1 more [java] Caused by: java.lang.ClassCastException: clojure.lang.Compiler$HoistedMethod cannot be cast to clojure.lang.Compiler$FnMethod [java] at clojure.lang.Compiler$FnExpr.parse(Compiler.java:4468) [java] at clojure.lang.Compiler.analyzeSeq(Compiler.java:7353) [java] ... 17 more |
Comment by Nicola Mometto [ 25/Aug/15 2:36 PM ] |
Looks like direct linking interacts with the diffs in this patch in non trivial ways. |
Comment by Kevin Downey [ 25/Aug/15 3:05 PM ] |
I must have screwed up running the tests some how, I definitely get the same error now |
Comment by Kevin Downey [ 25/Aug/15 4:00 PM ] |
after you get past the cast issuing (just adding some conditional logic there) it looks like HoistedMethodInvocationExpr needs to be made aware of if it is emitting in an instance method or a static method, and do the right thing with regard to this pointers and argument offsets. this will likely require HoistedMethod growing the ability to be a static method (and maybe preferring static methods when possible). if you cause HoistedMethod to set usesThis to true on methods that use it, then everything appears hunky-dory (if I ran the tests correctly), but this largely negates the new direct linking stuff, which is not good. |
Comment by Kevin Downey [ 27/Aug/15 9:30 PM ] |
hoistedmethod-pass-7.diff adds a single commit to hoistedmethod-pass-5.diff the single commit changes hoisted methods to always be static methods, and adjusts the arguments in the invocation of the hoisted method based on if the containing function is a static/direct function or not. again I haven't done the extended testing with this patch. here is an example of what the hoisted methods look like user> (println (disassemble (fn ^long [^long y] (let [x (try (/ 1 0) (catch Throwable t 0))] x)))) // Compiled from form-init3851661302895745152.clj (version 1.5 : 49.0, super bit) public final class user$eval1872$fn__1873 extends clojure.lang.AFunction implements clojure.lang.IFn$LL { // Field descriptor #9 Lclojure/lang/Var; public static final clojure.lang.Var const__0; // Field descriptor #11 Ljava/lang/Object; public static final java.lang.Object const__1; // Field descriptor #11 Ljava/lang/Object; public static final java.lang.Object const__2; // Method descriptor #14 ()V // Stack: 2, Locals: 0 public static {}; 0 ldc <String "clojure.core"> [16] 2 ldc <String "/"> [18] 4 invokestatic clojure.lang.RT.var(java.lang.String, java.lang.String) : clojure.lang.Var [24] 7 checkcast clojure.lang.Var [26] 10 putstatic user$eval1872$fn__1873.const__0 : clojure.lang.Var [28] 13 lconst_1 14 invokestatic java.lang.Long.valueOf(long) : java.lang.Long [34] 17 putstatic user$eval1872$fn__1873.const__1 : java.lang.Object [36] 20 lconst_0 21 invokestatic java.lang.Long.valueOf(long) : java.lang.Long [34] 24 putstatic user$eval1872$fn__1873.const__2 : java.lang.Object [38] 27 return Line numbers: [pc: 0, line: 1] // Method descriptor #14 ()V // Stack: 1, Locals: 1 public user$eval1872$fn__1873(); 0 aload_0 [this] 1 invokespecial clojure.lang.AFunction() [41] 4 return Line numbers: [pc: 0, line: 1] // Method descriptor #43 (J)J // Stack: 2, Locals: 4 public final long invokePrim(long y); 0 lload_1 [y] 1 invokestatic user$eval1872$fn__1873.__hoisted1874(long) : java.lang.Object [47] 4 astore_3 [x] 5 aload_3 [x] 6 aconst_null 7 astore_3 8 checkcast java.lang.Number [50] 11 invokevirtual java.lang.Number.longValue() : long [54] 14 lreturn Line numbers: [pc: 0, line: 1] Local variable table: [pc: 5, pc: 8] local: x index: 3 type: java.lang.Object [pc: 0, pc: 14] local: this index: 0 type: java.lang.Object [pc: 0, pc: 14] local: y index: 1 type: long // Method descriptor #59 (Ljava/lang/Object;)Ljava/lang/Object; // Stack: 5, Locals: 2 public java.lang.Object invoke(java.lang.Object arg0); 0 aload_0 [this] 1 aload_1 [arg0] 2 checkcast java.lang.Number [50] 5 invokestatic clojure.lang.RT.longCast(java.lang.Object) : long [63] 8 invokeinterface clojure.lang.IFn$LL.invokePrim(long) : long [65] [nargs: 3] 13 new java.lang.Long [30] 16 dup_x2 17 dup_x2 18 pop 19 invokespecial java.lang.Long(long) [68] 22 areturn // Method descriptor #45 (J)Ljava/lang/Object; // Stack: 4, Locals: 4 public static java.lang.Object __hoisted1874(long arg0); 0 lconst_1 1 lconst_0 2 invokestatic clojure.lang.Numbers.divide(long, long) : java.lang.Number [74] 5 astore_2 6 goto 17 9 astore_3 [t] 10 getstatic user$eval1872$fn__1873.const__2 : java.lang.Object [38] 13 astore_2 14 goto 17 17 aload_2 18 areturn Exception Table: [pc: 0, pc: 6] -> 9 when : java.lang.Throwable Line numbers: [pc: 0, line: 1] [pc: 2, line: 1] Local variable table: [pc: 9, pc: 14] local: t index: 3 type: java.lang.Object [pc: 0, pc: 18] local: y index: 1 type: java.lang.Object } |
Comment by Kevin Downey [ 27/Aug/15 9:43 PM ] |
there is still an issue with patch 7, (defn f [^long y] (let [x (try (+ 1 0) (catch Throwable t y))] x)) causes a verifier error |
Comment by Nicola Mometto [ 28/Aug/15 11:41 AM ] |
Patch 0001-CLJ-701-add-HoistedMethod-to-the-compiler-for-hoisti.patch is patch hoistedmethod-pass-7 with the following changes:
Authorship is maintained for |
Comment by Kevin Downey [ 28/Aug/15 5:40 PM ] |
maybe we need a "Bronsa's Guide to Submitting Patches" to supplement http://dev.clojure.org/display/community/Developing+Patches, I had no idea a single commit was preferred, but that makes sense given the format, although I just noticed the bit on deleting old patches to avoid confusion. Is there a preferred format for patch names too? |
Comment by Nicola Mometto [ 28/Aug/15 6:08 PM ] |
Kevin, having a single commit per patch is something that I've seen Rich and Alex ask for in a bunch of tickets, as I guess it makes it easier to evaluate the overall diff (even though it sacrifices granularity of description). One thing I personally prefer is to add the ticket name at the beginning of the commit message, it makes it easier to understand changes when using e.g. git blame |
Comment by Andy Fingerhut [ 28/Aug/15 6:33 PM ] |
Just now I added a suggestion to http://dev.clojure.org/display/community/Developing+Patches that one read their patches before attaching them, and remove any spurious white space changes. Also to consider submitting patches with a single commit, rather than ones broken up into multiple commits, as reviewers tend to prefer those. Alex Miller recently edited that page with the note about putting the ticket id first in the commit comment. Only preference on patch file names is the one on that page – that they end with '.patch' or '.diff', because Rich's preferred editor for reading them recognizes those suffixes and displays the file in a patch-specific mode, I would guess. |
Comment by Nicola Mometto [ 28/Aug/15 6:38 PM ] |
0001-CLJ-701-add-HoistedMethod-to-the-compiler-for-hoisti-v2.patch is the same as 0001-CLJ-701-add-HoistedMethod-to-the-compiler-for-hoisti.patch but it changes some indentation to avoid mixing tabs and spaces |
Comment by Michael Blume [ 01/Sep/15 5:27 PM ] |
I have a verify error on the latest patch, will attempt to provide a small test case: Exception in thread "main" java.lang.VerifyError: (class: com/climate/scouting/homestead_test$fn__17125, method: invokeStatic signature: (Ljava/lang/Object;)Ljava/lang/Object |
Comment by Michael Blume [ 01/Sep/15 6:29 PM ] |
https://github.com/MichaelBlume/hoist-fail I'd rather remove the dependency on compojure-api but I'm having trouble reproducing without it |
Comment by Michael Blume [ 01/Sep/15 8:01 PM ] |
Ok, less stupid test case: (let [^boolean foo true] (do (try foo (catch Throwable t)) nil)) |
Comment by Michael Blume [ 01/Sep/15 8:30 PM ] |
...I wonder how hard it would be to write a generative test which produced small snippets of valid Clojure code, compiled them, and checked for errors. I bet we could've caught this. |
Comment by Nicola Mometto [ 02/Sep/15 12:53 AM ] |
(let [^boolean a true]) I'd say we wait for what (I rest my case that if we had some better spec on what type-hints are supposed to be valid and some better compile-time validation on them, issues like this would not arise) |
Comment by Nicola Mometto [ 02/Sep/15 1:13 AM ] |
If this is considered a bug, the fix is trivial btw @@ -5888,7 +5888,7 @@ public static class LocalBinding{ public boolean hasJavaClass() { if(init != null && init.hasJavaClass() - && Util.isPrimitive(init.getJavaClass()) + && (Util.isPrimitive(init.getJavaClass()) || Util.isPrimitive(tag)) && !(init instanceof MaybePrimitiveExpr)) return false; return tag != null |
Comment by Kevin Downey [ 03/Sep/15 11:22 AM ] |
I imagine ^boolean type hints (that don't do anything and are ignored) are going to become very common in clojure code, given everyone's keen interest in code sharing between clojure and clojurescript, and iirc clojurescript actually using ^boolean |
Comment by Nicola Mometto [ 12/Sep/15 5:33 AM ] |
Last patch doesn't compile anymore since it hits the bug reported in |
Comment by Alex Miller [ 18/Sep/15 8:28 AM ] |
Moving to incomplete for now since it seems to be blocked on the other ticket |
Comment by Kevin Downey [ 19/Jan/16 4:06 PM ] |
0002-CLJ-701-add-HoistedMethod-to-the-compiler-for-hoisti.patch makes the change that Nicola suggested (with an extra null check). Michael's test case with the ^boolean type hint compiles now |
Comment by Michael Blume [ 19/Jan/16 6:01 PM ] |
Maybe this is out of scope for this ticket since it's just existing code that you're moving around, but I've always been confused by //exception should be on stack
gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE), finallyLocal);
finallyExpr.emit(C.STATEMENT, objx, gen);
gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ILOAD), finallyLocal);
If we emit finallyExpr as C.STATEMENT, it should have no impact on the stack, and the exception should just be there without us needing to store and load it, right? |
Comment by Michael Blume [ 19/Jan/16 6:02 PM ] |
Also the latest patch seems to add a patch file to the root directory |
Comment by Michael Blume [ 19/Jan/16 7:01 PM ] |
New failing code (defn foo [req] (let [^boolean b (get req :is-baz)] (do (try :bar) :foo))) |
Comment by Kevin Downey [ 20/Jan/16 12:05 AM ] |
the latest patch applied to master is causing some test failures for data.xml Testing clojure.data.xml.test-seq-tree FAIL in (release-head-top) (test_seq_tree.clj:52) expected: (= nil (.get input-ref)) actual: (not (= nil (0 1 2 3 4 5 6 7 8 9))) FAIL in (release-head-nested-late) (test_seq_tree.clj:60) expected: (= nil (.get input-ref)) actual: (not (= nil (1 2 :< 3 4 5 :>)) |
Comment by Nicola Mometto [ 20/Jan/16 5:54 AM ] |
WRT ^boolean type hints going to be common because clojurescript uses them – I don't think we should accept invalid code for the sake of interoparability. See |
Comment by Kevin Downey [ 19/Apr/16 1:17 PM ] |
I've just got around to trying to dig in to the data.xml failures I mentioned above. Both those tests are related to head holding on a lazy seq. Both tests reliably fail with 0002-CLJ-701-add-HoistedMethod-to-the-compiler-for-hoisti.patch So I suspect the hoisted method isn't properly clearing locals. |
Comment by Kevin Downey [ 19/Apr/16 5:56 PM ] |
so I don't forget, I realized the issue with data.xml is because the 'is' macro in the test expands in to a try/catch in an expression context, which is hoisted, and the hoist causes the whole environment not to be cleared until the hoisted method returns, which is obviously not correct. |