Skip to content

Commit bf67f94

Browse files
committedDec 1, 2014
Run keyword arg specs
1 parent 8463d38 commit bf67f94

File tree

3 files changed

+127
-8
lines changed

3 files changed

+127
-8
lines changed
 

‎lib/opal/nodes/def.rb

+62-8
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ def compile
1515

1616
opt = args[1..-1].select { |a| a.first == :optarg }
1717

18+
@kwargs = args[1..-1].select do |arg|
19+
[:kwarg, :kwoptarg, :kwrestarg].include? arg.first
20+
end
21+
1822
argc = args.length - 1
1923

2024
# block name (&block)
@@ -35,7 +39,7 @@ def compile
3539
end
3640

3741
if compiler.arity_check?
38-
arity_code = arity_check(args, opt, uses_splat, block_name, mid)
42+
arity_code = arity_check(args, opt, uses_splat, @kwargs, block_name, mid)
3943
end
4044

4145
in_scope do
@@ -64,6 +68,8 @@ def compile
6468
line "}"
6569
end
6670

71+
compile_keyword_args
72+
6773
# must do this after opt args incase opt arg uses yield
6874
scope_name = scope.identity
6975

@@ -112,6 +118,39 @@ def compile
112118
wrap '(', ", nil) && '#{mid}'" if expr?
113119
end
114120

121+
def compile_keyword_args
122+
return if @kwargs.empty?
123+
helper :hash2
124+
125+
line "if ($kwargs == null) {"
126+
line " $kwargs = $hash2([], {});"
127+
line "}"
128+
line "if (!$kwargs.$$is_hash) {"
129+
line " throw new Error('expecting keyword args');"
130+
line "}"
131+
132+
@kwargs.each do |kwarg|
133+
case kwarg.first
134+
when :kwoptarg
135+
arg_name = kwarg[1]
136+
var_name = variable(arg_name.to_s)
137+
line "if ((#{var_name} = $kwargs.smap['#{arg_name}']) == null) {"
138+
line " #{var_name} = ", expr(kwarg[2])
139+
line "}"
140+
when :kwarg
141+
arg_name = kwarg[1]
142+
var_name = variable(arg_name.to_s)
143+
line "if ((#{var_name} = $kwargs.smap['#{arg_name}']) == null) {"
144+
line " throw new Error('expecting keyword arg: #{arg_name}')"
145+
line "}"
146+
when :kwrestarg
147+
nil
148+
else
149+
raise "unknown kwarg type #{kwarg.first}"
150+
end
151+
end
152+
end
153+
115154
# Simple helper to check whether this method should be defined through
116155
# `Opal.defn()` runtime helper.
117156
#
@@ -129,14 +168,18 @@ def uses_defn?(scope)
129168
end
130169

131170
# Returns code used in debug mode to check arity of method call
132-
def arity_check(args, opt, splat, block_name, mid)
171+
def arity_check(args, opt, splat, kwargs, block_name, mid)
133172
meth = mid.to_s.inspect
134173

135174
arity = args.size - 1
136175
arity -= (opt.size)
176+
137177
arity -= 1 if splat
178+
179+
arity -= (kwargs.size)
180+
138181
arity -= 1 if block_name
139-
arity = -arity - 1 if !opt.empty? or splat
182+
arity = -arity - 1 if !opt.empty? or !kwargs.empty? or splat
140183

141184
# $arity will point to our received arguments count
142185
aritycode = "var $arity = arguments.length;"
@@ -154,15 +197,26 @@ class ArgsNode < Base
154197
handle :args
155198

156199
def compile
200+
done_kwargs = false
157201
children.each_with_index do |child, idx|
158202
next if :blockarg == child.first
159203
next if :restarg == child.first and child[1].nil?
160204

161-
child = child[1].to_sym
162-
push ', ' unless idx == 0
163-
child = variable(child)
164-
scope.add_arg child.to_sym
165-
push child.to_s
205+
case child.first
206+
when :kwarg, :kwoptarg, :kwrestarg
207+
unless done_kwargs
208+
done_kwargs = true
209+
push ', ' unless idx == 0
210+
scope.add_arg '$kwargs'
211+
push '$kwargs'
212+
end
213+
else
214+
child = child[1].to_sym
215+
push ', ' unless idx == 0
216+
child = variable(child)
217+
scope.add_arg child.to_sym
218+
push child.to_s
219+
end
166220
end
167221
end
168222
end

‎opal/corelib/hash.rb

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
class Hash
44
include Enumerable
55

6+
# Mark all hash instances as valid hashes (used to check keyword args, etc)
7+
`def.$$is_hash = true`
8+
69
def self.[](*objs)
710
`Opal.hash.apply(null, objs)`
811
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# TODO: this should be loaded from rubyspec instead
2+
3+
describe "An instance method with keyword arguments" do
4+
context "when there is a single keyword argument" do
5+
before do
6+
def foo(a: 1)
7+
a
8+
end
9+
end
10+
11+
it "evaluates to the default when a value isn't provided" do
12+
foo.should == 1
13+
end
14+
15+
it "evaluates to the provided value" do
16+
foo(a: 20).should == 20
17+
foo(a: nil).should be_nil
18+
end
19+
20+
it "raises an argument error when an unknown keyword argument is provided" do
21+
lambda { foo(b: 20) }.should raise_error(ArgumentError)
22+
end
23+
24+
it "raises an argument error when a non-keyword argument is provided" do
25+
lambda { foo(1) }.should raise_error(ArgumentError)
26+
end
27+
end
28+
29+
it "treats a sole hash argument correctly" do
30+
def foo(a, b: 10)
31+
[a, b]
32+
end
33+
foo(b: "b").should == [{:b => "b"}, 10]
34+
foo("a", b: "b").should == ["a", "b"]
35+
end
36+
37+
it "correctly distinguishes between optional and keyword arguments" do
38+
def foo(a = true, b: 10)
39+
[a, b]
40+
end
41+
foo(b: 42).should == [true, 42]
42+
foo(false, b: 42).should == [false, 42]
43+
end
44+
45+
it "correctly distinguishes between rest and keyword arguments" do
46+
def foo(*a, b: 10)
47+
[a, b]
48+
end
49+
foo(1, 2, 3, 4).should == [[1, 2, 3, 4], 10]
50+
foo(1, 2, 3, 4, b: 42).should == [[1, 2, 3, 4], 42]
51+
foo(b: 42).should == [[], 42]
52+
end
53+
54+
it "should allow keyword rest arguments" do
55+
def foo(a: 1, **b)
56+
[a, b]
57+
end
58+
foo(b: 2, c: 3, d: 4).should == [1, {:b => 2, :c => 3, :d => 4}]
59+
foo(a: 4, b: 2).should == [4, {:b => 2}]
60+
foo.should == [1, {}]
61+
end
62+
end

0 commit comments

Comments
 (0)
Please sign in to comment.