Skip to content

Commit 6dffcff

Browse files
author
Ary Borenszweig
committedMay 20, 2017
Fixed #236: macro lookup conflicts with method lookup when including into toplevel
1 parent 6b1067a commit 6dffcff

File tree

7 files changed

+95
-23
lines changed

7 files changed

+95
-23
lines changed
 

‎spec/compiler/semantic/doc_spec.cr

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ describe "Semantic: doc" do
145145
), wants_doc: true
146146
program = result.program
147147
foo = program.types["Foo"]
148-
bar = foo.metaclass.lookup_macros("bar").not_nil!.first
148+
bar = foo.metaclass.lookup_macros("bar").as(Array(Macro)).first
149149
bar.doc.should eq("Hello")
150150
end
151151

‎spec/compiler/semantic/macro_spec.cr

+34
Original file line numberDiff line numberDiff line change
@@ -1026,4 +1026,38 @@ describe "Semantic: macro" do
10261026
res.program.types.has_key?("D").should be_false
10271027
end
10281028
end
1029+
1030+
it "finds method before macro (#236)" do
1031+
assert_type(%(
1032+
macro global
1033+
1
1034+
end
1035+
1036+
class Foo
1037+
def global
1038+
'a'
1039+
end
1040+
1041+
def bar
1042+
global
1043+
end
1044+
end
1045+
1046+
Foo.new.bar
1047+
)) { char }
1048+
end
1049+
1050+
it "finds macro and method at the same scope" do
1051+
assert_type(%(
1052+
macro global(x)
1053+
1
1054+
end
1055+
1056+
def global(x, y)
1057+
'a'
1058+
end
1059+
1060+
{global(1), global(1, 2)}
1061+
)) { tuple_of [int32, char] }
1062+
end
10291063
end

‎src/compiler/crystal/semantic/call.cr

+11-5
Original file line numberDiff line numberDiff line change
@@ -637,23 +637,29 @@ class Crystal::Call
637637
end
638638

639639
def lookup_macro
640-
in_macro_target &.lookup_macro(name, args, named_args)
640+
in_macro_target do |target|
641+
result = target.lookup_macro(name, args, named_args)
642+
case result
643+
when Macro
644+
return result
645+
when Type::DefInMacroLookup
646+
return nil
647+
end
648+
end
641649
end
642650

643651
def in_macro_target
644652
if with_scope = @with_scope
645-
with_scope = with_scope.metaclass unless with_scope.metaclass?
646653
macros = yield with_scope
647654
return macros if macros
648655
end
649656

650657
node_scope = scope
651658
node_scope = node_scope.base_type if node_scope.is_a?(VirtualType)
652-
node_scope = node_scope.metaclass unless node_scope.metaclass?
653659

654660
macros = yield node_scope
655-
if !macros && node_scope.metaclass? && node_scope.instance_type.module?
656-
macros = yield program.object.metaclass
661+
if !macros && node_scope.module?
662+
macros = yield program.object
657663
end
658664

659665
macros ||= yield program

‎src/compiler/crystal/semantic/call_error.cr

+1-1
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ class Crystal::Call
484484
return if obj && !obj.is_a?(Path)
485485

486486
macros = in_macro_target &.lookup_macros(def_name)
487-
return unless macros
487+
return unless macros.is_a?(Array(Macro))
488488
macros = macros.reject &.visibility.private?
489489

490490
if macros.size == 1

‎src/compiler/crystal/semantic/method_missing.cr

+2-10
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,8 @@ module Crystal
2323
end
2424

2525
def lookup_method_missing
26-
# method_missing is actually stored in the metaclass
27-
method_missing = metaclass.lookup_macro("method_missing", ONE_ARG, nil)
28-
return method_missing if method_missing
29-
30-
parents.try &.each do |parent|
31-
method_missing = parent.lookup_method_missing
32-
return method_missing if method_missing
33-
end
34-
35-
nil
26+
a_macro = lookup_macro("method_missing", ONE_ARG, nil)
27+
a_macro.is_a?(Macro) ? a_macro : nil
3628
end
3729

3830
def define_method_from_method_missing(method_missing, signature, original_call)

‎src/compiler/crystal/semantic/semantic_visitor.cr

+2-2
Original file line numberDiff line numberDiff line change
@@ -248,15 +248,15 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor
248248
macro_scope = macro_scope.remove_alias
249249

250250
the_macro = macro_scope.metaclass.lookup_macro(node.name, node.args, node.named_args)
251-
node.raise "private macro '#{node.name}' called for #{obj}" if the_macro && the_macro.visibility.private?
251+
node.raise "private macro '#{node.name}' called for #{obj}" if the_macro.is_a?(Macro) && the_macro.visibility.private?
252252
when Nil
253253
return false if node.name == "super" || node.name == "previous_def"
254254
the_macro = node.lookup_macro
255255
else
256256
return false
257257
end
258258

259-
return false unless the_macro
259+
return false unless the_macro.is_a?(Macro)
260260

261261
# If we find a macro outside a def/block and this is not the first pass it means that the
262262
# macro was defined before we first found this call, so it's an error

‎src/compiler/crystal/types.cr

+44-4
Original file line numberDiff line numberDiff line change
@@ -376,25 +376,57 @@ module Crystal
376376
defs.try(&.has_key?(name)) || parents.try(&.any?(&.has_def?(name)))
377377
end
378378

379+
record DefInMacroLookup
380+
381+
# Looks up a macro with the give name and matching the given args
382+
# and named_args. Returns:
383+
# - a `Macro`, if found
384+
# - `nil`, if not found
385+
# - `DefInMacroLookup` if not found and a Def was found instead
386+
#
387+
# In the case of `DefInMacroLookup`, it means that macros shouldn't
388+
# be looked up in implicit enclosing scopes such as Object
389+
# or the Program.
379390
def lookup_macro(name, args : Array, named_args)
380-
if macros = self.macros.try &.[name]?
391+
# Macros are always stored in a type's metaclass
392+
macros_scope = self.metaclass? ? self : self.metaclass
393+
394+
if macros = macros_scope.macros.try &.[name]?
381395
match = macros.find &.matches?(args, named_args)
382396
return match if match
383397
end
384398

385-
instance_type.parents.try &.each do |parent|
386-
parent_macro = parent.metaclass.lookup_macro(name, args, named_args)
399+
# First check if there are defs at this scope with that name.
400+
# If so, make that a priority in the lookup and don't consider
401+
# macro matches.
402+
if has_def?(name)
403+
return DefInMacroLookup.new
404+
end
405+
406+
parents.try &.each do |parent|
407+
parent_macro = parent.lookup_macro(name, args, named_args)
387408
return parent_macro if parent_macro
388409
end
389410

390411
nil
391412
end
392413

414+
# Looks up macros with the given name. Returns:
415+
# - an Array of Macro if found
416+
# - `nil` if not found
417+
# - `DefInMacroLookup` if not found and some Defs were found instead
393418
def lookup_macros(name)
394-
if macros = self.macros.try &.[name]?
419+
# Macros are always stored in a type's metaclass
420+
macros_scope = self.metaclass? ? self : self.metaclass
421+
422+
if macros = macros_scope.macros.try &.[name]?
395423
return macros
396424
end
397425

426+
if has_def?(name)
427+
return DefInMacroLookup.new
428+
end
429+
398430
parents.try &.each do |parent|
399431
parent_macros = parent.lookup_macros(name)
400432
return parent_macros if parent_macros
@@ -950,6 +982,14 @@ module Crystal
950982
def vars?
951983
@vars
952984
end
985+
986+
def metaclass?
987+
true
988+
end
989+
990+
def metaclass
991+
self
992+
end
953993
end
954994

955995
# Abstract base type for classes and structs

0 commit comments

Comments
 (0)
Please sign in to comment.