@@ -4,18 +4,18 @@ module Crystal
4
4
class Type
5
5
ONE_ARG = [Arg .new(" a1" )]
6
6
7
- def check_method_missing (signature )
7
+ def check_method_missing (signature, call )
8
8
if ! metaclass? && signature.name != " initialize"
9
9
# Make sure to define method missing in the whole hierarchy
10
10
virtual_type = virtual_type()
11
11
if virtual_type == self
12
12
method_missing = lookup_method_missing
13
13
if method_missing
14
- define_method_from_method_missing(method_missing, signature)
14
+ define_method_from_method_missing(method_missing, signature, call )
15
15
return true
16
16
end
17
17
else
18
- return virtual_type.check_method_missing(signature)
18
+ return virtual_type.check_method_missing(signature, call )
19
19
end
20
20
end
21
21
@@ -35,7 +35,7 @@ module Crystal
35
35
nil
36
36
end
37
37
38
- def define_method_from_method_missing (method_missing, signature)
38
+ def define_method_from_method_missing (method_missing, signature, original_call )
39
39
name_node = StringLiteral .new(signature.name)
40
40
args_nodes = [] of ASTNode
41
41
args_nodes_names = Set (String ).new
@@ -62,22 +62,63 @@ module Crystal
62
62
fake_call = Call .new(nil , " method_missing" , [call] of ASTNode )
63
63
64
64
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
68
65
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
71
92
72
93
owner = self
73
94
owner = owner.base_type if owner.is_a?(VirtualType )
74
95
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)
80
108
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
81
122
end
82
123
end
83
124
@@ -86,18 +127,18 @@ module Crystal
86
127
end
87
128
88
129
class VirtualType
89
- def check_method_missing (signature )
130
+ def check_method_missing (signature, call )
90
131
method_missing = base_type.lookup_method_missing
91
132
defined = false
92
133
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
94
135
end
95
136
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
97
138
defined
98
139
end
99
140
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 )
101
142
defined = false
102
143
103
144
base_type.subclasses.each do |subclass |
@@ -111,19 +152,29 @@ module Crystal
111
152
112
153
# Check if the subclass redefined the method_missing
113
154
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 )
115
156
defined = true
116
157
elsif method_missing
117
158
# 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 )
119
160
subclass_method_missing = method_missing
120
161
defined = true
121
162
end
122
163
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
124
165
end
125
166
126
167
defined
127
168
end
128
169
end
129
170
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