Skip to content

Commit

Permalink
[interpreter,spec] (myst-lang#109) Fix self and scope resolution fo…
Browse files Browse the repository at this point in the history
…r blocks on static methods.

`Invocation` was incorrectly determining the value of `self` to use when calling a function. Before myst-lang#131, it was only checking if the Call had a receiver, and would use that value if it did. After myst-lang#131, if the Call did _not_ have a receiver, it would then check if the functor was a closure, and use the closed value of `self` if it was.

Now, Invocations _always_ perform both checks, with closures taking higher precedence than Call receivers (though, both will be pushed to the stack if the conditions are met).

Before this commit, I was very confused why static methods weren't working while non-static methods were. I assumed it was an issue with `scope` vs `instance_scope` on Types or something like that, but had no idea.

Now, though, I'm even more confused how _any_ case was working previously, as the Call receiver was _always_ being pushed as the value of `self`. At least with this version of the code, the value of `self` that will be present is more explicit and well-specified. ¯\_(ツ)_/¯
  • Loading branch information
faultyserver committed Jan 27, 2018
1 parent fe72e5a commit c8eb378
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 33 deletions.
217 changes: 217 additions & 0 deletions spec/interpreter/misc/closures_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
require "../../spec_helper.cr"
require "../../support/nodes.cr"
require "../../support/interpret.cr"

describe "Interpreter - Closures" do
describe "on blocks" do
it "captures the value of `self` as part of the closure" do
itr = parse_and_interpret %q(
@sum = 0
[1, 2, 3].each{ |e| @sum += e }
@sum
)

itr.stack.last.should eq(val(6))
end

it "can access variables from blocks on static methods (see #109)" do
itr = parse_and_interpret %q(
deftype Test
defstatic hello(&block)
block()
end
end
i = 0
Test.hello do
i += 6
end
i
)

itr.stack.last.should eq(val(6))
end

it "can access variables from within nested blocks" do
itr = parse_and_interpret %q(
defmodule Test
def hello(&block)
block()
end
end
i = 0
Test.hello do
[1, 2, 3].each{ |e| i += e }
end
i
)

itr.stack.last.should eq(val(6))
end

it "can access variables from within nested blocks on static methods (see #109)" do
itr = parse_and_interpret %q(
deftype Test
defstatic hello(&block)
block()
end
end
i = 0
Test.hello do
[1, 2, 3].each{ |e| i += e }
end
i
)

itr.stack.last.should eq(val(6))
end

it "maintains the top level value of `self` from within nested blocks" do
itr = parse_and_interpret %q(
deftype Test
defstatic hello(&block)
block()
end
end
@sum = 0
Test.hello do
[1, 2, 3].each{ |e| @sum += e }
end
@sum
)

itr.stack.last.should eq(val(6))
end

it "can access variables from a block within an anonymous function" do
itr = parse_and_interpret %q(
defmodule Test
def hello(&block)
block()
end
end
i = 0
func = fn
->() { [1, 2, 3].each{ |e| i += e }; i }
end
Test.hello(&func)
)

itr.stack.last.should eq(val(6))
end

it "can access variables from a block within an anonymous function on static methods (see #109)" do
itr = parse_and_interpret %q(
deftype Test
defstatic hello(&block)
block()
end
end
i = 0
func = fn
->() { [1, 2, 3].each{ |e| i += e }; i }
end
Test.hello(&func)
)

itr.stack.last.should eq(val(6))
end
end


describe "on anonymous functions" do
it "captures the value of `self` as part of the closure" do
itr = parse_and_interpret %q(
@sum = 0
func = fn
->(a) { @sum += a }
end
func(6)
@sum
)

itr.stack.last.should eq(val(6))
end

it "can access variables from anonymous functions on static methods (see #109)" do
itr = parse_and_interpret %q(
deftype Test
defstatic hello(&block)
block()
end
end
i = 0
Test.hello(&fn ->() { i += 6 } end)
i
)

itr.stack.last.should eq(val(6))
end

it "can access variables from within a nested block" do
itr = parse_and_interpret %q(
defmodule Test
def hello(&block)
block()
end
end
i = 0
Test.hello do
func = fn ->(e) { i += e } end
[1, 2, 3].each(&func)
end
i
)

itr.stack.last.should eq(val(6))
end

it "can access variables from within a nested block on static methods (see #109)" do
itr = parse_and_interpret %q(
deftype Test
defstatic hello(&block)
block()
end
end
i = 0
Test.hello do
func = fn ->(e) { i += e } end
[1, 2, 3].each(&func)
end
i
)

itr.stack.last.should eq(val(6))
end

it "maintains the top level value of `self` from within a nested block" do
itr = parse_and_interpret %q(
deftype Test
defstatic hello(&block)
block()
end
end
@sum = 0
Test.hello do
func = fn ->(e) { @sum += e } end
[1, 2, 3].each(&func)
end
@sum
)
end
end
end
14 changes: 0 additions & 14 deletions spec/interpreter/nodes/anonymous_function_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,6 @@ describe "Interpreter - AnonymousFunction" do
itr.stack.last.should eq(val(:called_foo))
end

it "captures the value of `self` as part of the closure" do
itr = parse_and_interpret %q(
@sum = 0
func = fn
->(a) { @sum += a }
end
func(6)
@sum
)

itr.stack.last.should eq(val(6))
end

it "allows clauses of various arities" do
itr = parse_and_interpret %q(
fn
Expand Down
10 changes: 0 additions & 10 deletions spec/interpreter/nodes/block_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,4 @@ describe "Interpreter - Block" do

itr.stack.last.should eq(val(6))
end

it "captures the value of `self` as part of the closure" do
itr = parse_and_interpret %q(
@sum = 0
[1, 2, 3].each{ |e| @sum += e }
@sum
)

itr.stack.last.should eq(val(6))
end
end
14 changes: 5 additions & 9 deletions src/myst/interpreter/invocation.cr
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,11 @@ module Myst

def invoke
@selfstack_size_at_entry = @itr.self_stack.size
case
when @receiver
# If the invocation has a receiver, use it as the current value of `self`
# for the duration of the Invocation.
@itr.push_self(@receiver.not_nil!)
when @func.closure?
# If the invoked functor is a closure, use the closed value of `self`.
@itr.push_self(@func.closed_self)
end
# If the invocation has a receiver, use it as the current value of `self`
# for the duration of the Invocation.
@itr.push_self(@receiver.not_nil!) if @receiver
# If the invoked functor is a closure, use the closed value of `self`.
@itr.push_self(@func.closed_self) if @func.closure?

result = @func.clauses.each do |clause|
@itr.push_scope_override(@func.new_scope)
Expand Down
13 changes: 13 additions & 0 deletions test.mt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
deftype Test
defstatic hello(&block)
block()
end
end

i = 0

func = fn
->() { 10.times{ i += 1 } }
end

Test.hello(&func)

0 comments on commit c8eb378

Please sign in to comment.