Skip to content

Commit 6b098eb

Browse files
author
Ary Borenszweig
committedDec 1, 2016
Let method_missing also generate a def
1 parent 135fc1c commit 6b098eb

File tree

4 files changed

+120
-22
lines changed

4 files changed

+120
-22
lines changed
 

Diff for: ‎spec/compiler/codegen/method_missing_spec.cr

+14
Original file line numberDiff line numberDiff line change
@@ -367,4 +367,18 @@ describe "Code gen: method_missing" do
367367
Foo.new(Wrapped.new).foo(1, 2, 3)
368368
)).to_i.should eq(6)
369369
end
370+
371+
it "does method_missing generating method" do
372+
run(%(
373+
class Foo
374+
macro method_missing(call)
375+
def {{call.name}}
376+
{{call.name.stringify}}
377+
end
378+
end
379+
end
380+
381+
Foo.new.bar
382+
)).to_string.should eq("bar")
383+
end
370384
end

Diff for: ‎spec/compiler/semantic/method_missing_spec.cr

+33
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,37 @@ describe "Semantic: method_missing" do
4040
Foo(Int32).new.foo
4141
)) { int32 }
4242
end
43+
44+
it "errors if method_missing expands to an incorrect method" do
45+
assert_error %(
46+
class Foo
47+
macro method_missing(call)
48+
def baz
49+
1
50+
end
51+
end
52+
end
53+
54+
Foo.new.bar
55+
),
56+
"wrong method_missing expansion"
57+
end
58+
59+
it "errors if method_missing expands to multiple methods" do
60+
assert_error %(
61+
class Foo
62+
macro method_missing(call)
63+
def bar
64+
1
65+
end
66+
67+
def qux
68+
end
69+
end
70+
end
71+
72+
Foo.new.bar
73+
),
74+
"wrong method_missing expansion"
75+
end
4376
end

Diff for: ‎src/compiler/crystal/semantic/call.cr

+1-1
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ class Crystal::Call
234234
end
235235

236236
if matches.empty?
237-
defined_method_missing = owner.check_method_missing(signature)
237+
defined_method_missing = owner.check_method_missing(signature, self)
238238
if defined_method_missing
239239
matches = owner.lookup_matches(signature)
240240
end

Diff for: ‎src/compiler/crystal/semantic/method_missing.cr

+72-21
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@ module Crystal
44
class Type
55
ONE_ARG = [Arg.new("a1")]
66

7-
def check_method_missing(signature)
7+
def check_method_missing(signature, call)
88
if !metaclass? && signature.name != "initialize"
99
# Make sure to define method missing in the whole hierarchy
1010
virtual_type = virtual_type()
1111
if virtual_type == self
1212
method_missing = lookup_method_missing
1313
if method_missing
14-
define_method_from_method_missing(method_missing, signature)
14+
define_method_from_method_missing(method_missing, signature, call)
1515
return true
1616
end
1717
else
18-
return virtual_type.check_method_missing(signature)
18+
return virtual_type.check_method_missing(signature, call)
1919
end
2020
end
2121

@@ -35,7 +35,7 @@ module Crystal
3535
nil
3636
end
3737

38-
def define_method_from_method_missing(method_missing, signature)
38+
def define_method_from_method_missing(method_missing, signature, original_call)
3939
name_node = StringLiteral.new(signature.name)
4040
args_nodes = [] of ASTNode
4141
args_nodes_names = Set(String).new
@@ -62,22 +62,63 @@ module Crystal
6262
fake_call = Call.new(nil, "method_missing", [call] of ASTNode)
6363

6464
expanded_macro = program.expand_macro method_missing, fake_call, self, self
65-
generated_nodes = program.parse_macro_source(expanded_macro, method_missing, method_missing, args_nodes_names) do |parser|
66-
parser.parse_to_def(a_def)
67-
end
6865

69-
a_def.body = generated_nodes
70-
a_def.yields = block.try &.args.size
66+
# Check if the expanded macro is a def. We do this
67+
# by just lexing the result and seeing if the first
68+
# token is `def`
69+
expands_to_def = starts_with_def?(expanded_macro)
70+
generated_nodes =
71+
program.parse_macro_source(expanded_macro, method_missing, method_missing, args_nodes_names) do |parser|
72+
if expands_to_def
73+
parser.parse
74+
else
75+
parser.parse_to_def(a_def)
76+
end
77+
end
78+
79+
if generated_nodes.is_a?(Def)
80+
a_def = generated_nodes
81+
else
82+
if expands_to_def
83+
raise_wrong_method_missing_expansion(
84+
"it should only expand to a single def",
85+
expanded_macro,
86+
original_call)
87+
end
88+
89+
a_def.body = generated_nodes
90+
a_def.yields = block.try &.args.size
91+
end
7192

7293
owner = self
7394
owner = owner.base_type if owner.is_a?(VirtualType)
7495

75-
if owner.is_a?(ModuleType)
76-
owner.add_def(a_def)
77-
true
78-
else
79-
false
96+
return false unless owner.is_a?(ModuleType)
97+
98+
owner.add_def(a_def)
99+
100+
# If it expanded to a def, we check if the def
101+
# is now found by regular lookup. It should!
102+
# Otherwise there's a mistake in the macro.
103+
if expands_to_def && owner.lookup_matches(signature).empty?
104+
raise_wrong_method_missing_expansion(
105+
"the generated method won't be found by the original call invocation",
106+
expanded_macro,
107+
original_call)
80108
end
109+
110+
true
111+
end
112+
113+
private def raise_wrong_method_missing_expansion(msg, expanded_macro, original_call)
114+
str = String.build do |io|
115+
io << "wrong method_missing expansion\n\n"
116+
io << "The method_missing macro expanded to:\n\n"
117+
io << Crystal.with_line_numbers(expanded_macro)
118+
io << "\n\n"
119+
io << "However, " << msg
120+
end
121+
original_call.raise str
81122
end
82123
end
83124

@@ -86,18 +127,18 @@ module Crystal
86127
end
87128

88129
class VirtualType
89-
def check_method_missing(signature)
130+
def check_method_missing(signature, call)
90131
method_missing = base_type.lookup_method_missing
91132
defined = false
92133
if method_missing
93-
defined = base_type.define_method_from_method_missing(method_missing, signature) || defined
134+
defined = base_type.define_method_from_method_missing(method_missing, signature, call) || defined
94135
end
95136

96-
defined = add_subclasses_method_missing_matches(base_type, method_missing, signature) || defined
137+
defined = add_subclasses_method_missing_matches(base_type, method_missing, signature, call) || defined
97138
defined
98139
end
99140

100-
def add_subclasses_method_missing_matches(base_type, method_missing, signature)
141+
def add_subclasses_method_missing_matches(base_type, method_missing, signature, call)
101142
defined = false
102143

103144
base_type.subclasses.each do |subclass|
@@ -111,19 +152,29 @@ module Crystal
111152

112153
# Check if the subclass redefined the method_missing
113154
if subclass_method_missing && subclass_method_missing.object_id != method_missing.object_id
114-
subclass.define_method_from_method_missing(subclass_method_missing, signature)
155+
subclass.define_method_from_method_missing(subclass_method_missing, signature, call)
115156
defined = true
116157
elsif method_missing
117158
# Otherwise, we need to define this method missing because of macro vars like @name
118-
subclass.define_method_from_method_missing(method_missing, signature)
159+
subclass.define_method_from_method_missing(method_missing, signature, call)
119160
subclass_method_missing = method_missing
120161
defined = true
121162
end
122163

123-
defined = add_subclasses_method_missing_matches(subclass, subclass_method_missing, signature) || defined
164+
defined = add_subclasses_method_missing_matches(subclass, subclass_method_missing, signature, call) || defined
124165
end
125166

126167
defined
127168
end
128169
end
129170
end
171+
172+
private def starts_with_def?(source)
173+
lexer = Crystal::Lexer.new(source)
174+
while true
175+
token = lexer.next_token
176+
return true if token.keyword?(:def)
177+
break if token.type == :EOF
178+
end
179+
false
180+
end

0 commit comments

Comments
 (0)
Please sign in to comment.