diff --git a/src/compiler/crystal/type_inference/call.cr b/src/compiler/crystal/type_inference/call.cr
index 65f8ef9a05a85545ba9c8d60308524b0fc4dcb37..532406afa9df12381a295125f579837b70017daa 100644
--- a/src/compiler/crystal/type_inference/call.cr
+++ b/src/compiler/crystal/type_inference/call.cr
@@ -124,33 +124,35 @@ module Crystal
     def lookup_matches_in_type(owner, self_type, def_name)
       arg_types = args.map &.type
 
+      signature = DefSignature.new(def_name, arg_types, block)
+
       matches = check_tuple_indexer(owner, def_name, args, arg_types)
-      matches ||= owner.lookup_matches(def_name, arg_types, block)
+      matches ||= owner.lookup_matches signature
 
       if matches.empty?
         if def_name == "new" && owner.metaclass? && (owner.instance_type.class? || owner.instance_type.virtual?) && !owner.instance_type.pointer?
           new_matches = define_new owner, arg_types
           unless new_matches.empty?
             if owner.virtual_metaclass?
-              matches = owner.lookup_matches(def_name, arg_types, block)
+              matches = owner.lookup_matches(signature)
             else
               matches = new_matches
             end
           end
         elsif !obj && owner != mod
-          mod_matches = mod.lookup_matches(def_name, arg_types, block)
+          mod_matches = mod.lookup_matches(signature)
           matches = mod_matches unless mod_matches.empty?
         end
       end
 
       if matches.empty? && owner.class? && owner.abstract
-        matches = owner.virtual_type.lookup_matches(def_name, arg_types, block)
+        matches = owner.virtual_type.lookup_matches(signature)
       end
 
       if matches.empty?
-        defined_method_missing = owner.check_method_missing(def_name, arg_types, block)
+        defined_method_missing = owner.check_method_missing(signature)
         if defined_method_missing
-          matches = owner.lookup_matches(def_name, arg_types, block)
+          matches = owner.lookup_matches(signature)
         end
       end
 
@@ -523,8 +525,7 @@ module Crystal
         if fun_literal_type
           if output = block_arg.fun.output
             block_type = (fun_literal_type as FunInstanceType).return_type
-            type_lookup = match.context.type_lookup as MatchesLookup
-            matched = type_lookup.match_arg(block_type, output, match.context)
+            matched = MatchesLookup.match_arg(block_type, output, match.context)
             unless matched
               raise "expected block to return #{output}, not #{block_type}"
             end
@@ -539,8 +540,7 @@ module Crystal
           raise "can't infer block type" unless block.body.type?
 
           block_type = block.body.type
-          type_lookup = match.context.type_lookup as MatchesLookup
-          matched = type_lookup.match_arg(block_type, output, match.context)
+          matched = MatchesLookup.match_arg(block_type, output, match.context)
           unless matched
             if output.is_a?(Self)
               raise "expected block to return #{match.context.owner}, not #{block_type}"
@@ -878,10 +878,10 @@ module Crystal
 
       # If there are no initialize at all, use parent's initialize
       if initializers.empty?
-        matches = instance_type.lookup_matches("initialize", arg_types, block)
+        matches = instance_type.lookup_matches DefSignature.new("initialize", arg_types, block)
       else
         # Otherwise, use this type initializers
-        matches = instance_type.lookup_matches_with_modules("initialize", arg_types, block)
+        matches = instance_type.lookup_matches_with_modules DefSignature.new("initialize", arg_types, block)
       end
 
       if matches.empty?
diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr
index c66953d481f019e4b022a39fa3162942a391f3e7..3e9c53fa693fb7ccce6ca5b4c7307ada8269c03b 100644
--- a/src/compiler/crystal/types.cr
+++ b/src/compiler/crystal/types.cr
@@ -241,11 +241,11 @@ module Crystal
       raise "Bug: #{self} doesn't implement undef"
     end
 
-    def lookup_matches(name, arg_types, block, owner = self, type_lookup = self, matches_array = nil)
+    def lookup_matches(signature, owner = self, type_lookup = self, matches_array = nil)
       raise "Bug: #{self} doesn't implement lookup_matches"
     end
 
-    def lookup_matches_with_modules(name, arg_types, block, owner = self, type_lookup = self, matches_array = nil)
+    def lookup_matches_with_modules(signature, owner = self, type_lookup = self, matches_array = nil)
       raise "Bug: #{self} doesn't implement lookup_matches_with_modules"
     end
 
@@ -285,7 +285,7 @@ module Crystal
       raise "Bug: #{self} doesn't implement lookup_macros"
     end
 
-    def check_method_missing(name, arg_types, block)
+    def check_method_missing(signature)
       false
     end
 
@@ -389,7 +389,7 @@ module Crystal
     def initialize(@program)
     end
 
-    def lookup_matches(name, arg_types, block, owner = self, type_lookup = self, matches_array = nil)
+    def lookup_matches(signature, owner = self, type_lookup = self, matches_array = nil)
       Matches.new(nil, nil, self, false)
     end
 
@@ -440,8 +440,10 @@ module Crystal
     end
   end
 
+  make_named_tuple DefSignature, [name, arg_types, block]
+
   module MatchesLookup
-    def match_def_args(arg_types, a_def, context)
+    def self.match_def_args(arg_types, a_def, context)
       matched_arg_types = nil
 
       splat_index = a_def.splat_index || -1
@@ -504,27 +506,27 @@ module Crystal
       Match.new(a_def, (matched_arg_types || arg_types), context)
     end
 
-    def match_arg(arg_type, arg : Arg, context : MatchContext)
+    def self.match_arg(arg_type, arg : Arg, context : MatchContext)
       restriction = arg.type? || arg.restriction
       arg_type.not_nil!.restrict restriction, context
     end
 
-    def match_arg(arg_type, restriction : ASTNode, context : MatchContext)
+    def self.match_arg(arg_type, restriction : ASTNode, context : MatchContext)
       arg_type.not_nil!.restrict restriction, context
     end
 
-    def lookup_matches_without_parents(name, arg_types, block, owner = self, type_lookup = self, matches_array = nil)
+    def lookup_matches_without_parents(signature, owner = self, type_lookup = self, matches_array = nil)
       if all_defs = self.defs()
-        if defs = all_defs[name]?
+        if defs = all_defs[signature.name]?
           found_defs = true
-          args_length = arg_types.length
-          yields = !!block
+          args_length = signature.arg_types.length
+          yields = !!signature.block
           context = MatchContext.new(owner, type_lookup)
 
           defs.each do |item|
             if (item.min_length <= args_length <= item.max_length) && item.yields == yields
               a_def = item.def
-              match = match_def_args(arg_types, a_def, context)
+              match = MatchesLookup.match_def_args(signature.arg_types, a_def, context)
 
               if match
                 matches_array ||= [] of Match
@@ -534,7 +536,7 @@ module Crystal
                 # we are done. We don't just compare types with ==, there is a special case:
                 # a function type with return T can be transpass a restriction of a function
                 # with with the same arguments but which returns Void.
-                if arg_types.equals?(match.arg_types) { |x, y| x.compatible_with?(y) }
+                if signature.arg_types.equals?(match.arg_types) { |x, y| x.compatible_with?(y) }
                   return Matches.new(matches_array, true, owner)
                 end
               end
@@ -543,38 +545,36 @@ module Crystal
         end
       end
 
-      Matches.new(matches_array, Cover.create(arg_types, matches_array), owner)
+      Matches.new(matches_array, Cover.create(signature.arg_types, matches_array), owner)
     end
 
-    def lookup_matches_with_modules(name, arg_types, block, owner = self, type_lookup = self, matches_array = nil)
-      matches = lookup_matches_without_parents(name, arg_types, block, owner, type_lookup, matches_array)
+    def lookup_matches_with_modules(signature, owner = self, type_lookup = self, matches_array = nil)
+      matches = lookup_matches_without_parents(signature, owner, type_lookup, matches_array)
       return matches unless matches.empty?
 
-      if (my_parents = parents) && !(name == "new" && owner.metaclass?)
+      if (my_parents = parents) && !(signature.name == "new" && owner.metaclass?)
         my_parents.each do |parent|
           break unless parent.is_a?(IncludedGenericModule) || parent.module?
 
-          matches = parent.lookup_matches_with_modules(name, arg_types, block, owner, parent, matches_array)
+          matches = parent.lookup_matches_with_modules(signature, owner, parent, matches_array)
           return matches unless matches.empty?
         end
       end
 
-      Matches.new(matches_array, Cover.create(arg_types, matches_array), owner, false)
+      Matches.new(matches_array, Cover.create(signature.arg_types, matches_array), owner, false)
     end
 
-    def lookup_matches(name, arg_types, block, owner = self, type_lookup = self, matches_array = nil)
-      # matches_array ||= [] of Match
-
-      matches = lookup_matches_without_parents(name, arg_types, block, owner, type_lookup, matches_array)
+    def lookup_matches(signature, owner = self, type_lookup = self, matches_array = nil)
+      matches = lookup_matches_without_parents(signature, owner, type_lookup, matches_array)
       return matches if matches.cover_all?
 
       matches_array = matches.matches
 
       cover = matches.cover
 
-      if (my_parents = parents) && !(name == "new" && owner.metaclass?)
+      if (my_parents = parents) && !(signature.name == "new" && owner.metaclass?)
         my_parents.each do |parent|
-          matches = parent.lookup_matches(name, arg_types, block, owner, parent, matches_array)
+          matches = parent.lookup_matches(signature, owner, parent, matches_array)
           return matches if matches.cover_all?
         end
       end
@@ -679,35 +679,35 @@ module Crystal
       nil
     end
 
-    def check_method_missing(name, arg_types, block)
-      if !metaclass? && name != "initialize"
+    def check_method_missing(signature)
+      if !metaclass? && signature.name != "initialize"
         # Make sure to define method missing in the whole hierarchy
         virtual_type = virtual_type()
         if virtual_type == self
           method_missing = lookup_method_missing
           if method_missing
-            define_method_from_method_missing(method_missing, name, arg_types, block)
+            define_method_from_method_missing(method_missing, signature)
             return true
           end
         else
-          return virtual_type.check_method_missing(name, arg_types, block)
+          return virtual_type.check_method_missing(signature)
         end
       end
 
       false
     end
 
-    def define_method_from_method_missing(method_missing, def_name, arg_types, block)
-      name_node = StringLiteral.new(def_name)
+    def define_method_from_method_missing(method_missing, signature)
+      name_node = StringLiteral.new(signature.name)
       args_nodes = [] of ASTNode
       args_nodes_names = Set(String).new
-      arg_types.each_index do |index|
+      signature.arg_types.each_index do |index|
         arg_node_name = "_arg#{index}"
         args_nodes << MacroId.new(arg_node_name)
         args_nodes_names << arg_node_name
       end
       args_node = ArrayLiteral.new(args_nodes)
-      if block
+      if block = signature.block
         block_vars = block.args.map_with_index do |var, index|
           Var.new("_block_arg#{index}")
         end
@@ -722,7 +722,7 @@ module Crystal
       generated_source = program.expand_macro self, method_missing, fake_call
       generated_nodes = program.parse_macro_source(generated_source, method_missing, method_missing, args_nodes_names)
 
-      a_def = Def.new(def_name, args_nodes_names.map { |name| Arg.new(name) }, generated_nodes)
+      a_def = Def.new(signature.name, args_nodes_names.map { |name| Arg.new(name) }, generated_nodes)
       a_def.yields = block.try &.args.length
 
       owner = self
@@ -1713,8 +1713,8 @@ module Crystal
       nil
     end
 
-    def check_method_missing(name, arg_types, block)
-      @generic_class.check_method_missing(name, arg_types, block)
+    def check_method_missing(signature)
+      @generic_class.check_method_missing(signature)
     end
 
     def to_s(io)
@@ -1920,12 +1920,12 @@ module Crystal
       @module.implements?(other_type)
     end
 
-    def lookup_matches(name, arg_types, block, owner = self, type_lookup = self, matches_array = nil)
-      @module.lookup_matches(name, arg_types, block, owner, type_lookup, matches_array)
+    def lookup_matches(signature, owner = self, type_lookup = self, matches_array = nil)
+      @module.lookup_matches(signature, owner, type_lookup, matches_array)
     end
 
-    def lookup_matches_without_parents(name, arg_types, block, owner = self, type_lookup = self, matches_array = nil)
-      @module.lookup_matches_without_parents(name, arg_types, block, owner, type_lookup, matches_array)
+    def lookup_matches_without_parents(signature, owner = self, type_lookup = self, matches_array = nil)
+      @module.lookup_matches_without_parents(signature, owner, type_lookup, matches_array)
     end
 
     def lookup_defs(name)
@@ -1948,10 +1948,6 @@ module Crystal
       @module.lookup_macros(name)
     end
 
-    def match_arg(arg_type, arg, owner, type_lookup, free_vars)
-      @module.match_arg(arg_type, arg, owner, type_lookup, free_vars)
-    end
-
     def has_def?(name)
       @module.has_def?(name)
     end
@@ -2093,8 +2089,8 @@ module Crystal
       @simple = true
     end
 
-    def lookup_matches(name, arg_types, block, owner = self, type_lookup = self)
-      aliased_type.lookup_matches(name, arg_types, block, owner, type_lookup)
+    def lookup_matches(signature, owner = self, type_lookup = self)
+      aliased_type.lookup_matches(signature, owner, type_lookup)
     end
 
     def lookup_defs(name)
@@ -2599,11 +2595,11 @@ module Crystal
   end
 
   module VirtualTypeLookup
-    def lookup_matches(name, arg_types, block, owner = self, type_lookup = self)
-      is_new = virtual_metaclass? && name == "new"
+    def lookup_matches(signature, owner = self, type_lookup = self)
+      is_new = virtual_metaclass? && signature.name == "new"
 
       base_type_lookup = virtual_lookup(base_type)
-      base_type_matches = base_type_lookup.lookup_matches(name, arg_types, block, self)
+      base_type_matches = base_type_lookup.lookup_matches(signature, self)
 
       # If there are no subclasses no need to look further
       if leaf?
@@ -2629,7 +2625,7 @@ module Crystal
           subtype_virtual_lookup = virtual_lookup(subtype.virtual_type)
 
           # Check matches but without parents: only included modules
-          subtype_matches = subtype_lookup.lookup_matches_with_modules(name, arg_types, block, subtype_virtual_lookup, subtype_virtual_lookup)
+          subtype_matches = subtype_lookup.lookup_matches_with_modules(signature, subtype_virtual_lookup, subtype_virtual_lookup)
 
           # For Foo+:Class#new we need to check that this subtype doesn't define
           # an incompatible initialize: if so, we return empty matches, because
@@ -2650,7 +2646,7 @@ module Crystal
             base_type_matches.each do |base_type_match|
               if base_type_match.def.return_type
                 # We need to check if the definition for the method is different than the one in the base type
-                full_subtype_matches = subtype_lookup.lookup_matches(name, arg_types, block, subtype_virtual_lookup, subtype_virtual_lookup)
+                full_subtype_matches = subtype_lookup.lookup_matches(signature, subtype_virtual_lookup, subtype_virtual_lookup)
                 if full_subtype_matches.any? { |match| match.def.same?(base_type_match.def) }
                   cloned_def = base_type_match.def.clone
                   cloned_def.macro_owner = base_type_match.def.macro_owner
@@ -2664,7 +2660,7 @@ module Crystal
             end
 
             if new_subtype_matches
-              subtype_matches = Matches.new(new_subtype_matches, Cover.create(arg_types, new_subtype_matches))
+              subtype_matches = Matches.new(new_subtype_matches, Cover.create(signature.arg_types, new_subtype_matches))
             end
           end
 
@@ -2681,7 +2677,7 @@ module Crystal
             end
 
             if new_subtype_matches
-              subtype_matches = Matches.new(new_subtype_matches, Cover.create(arg_types, new_subtype_matches))
+              subtype_matches = Matches.new(new_subtype_matches, Cover.create(signature.arg_types, new_subtype_matches))
             end
           end
 
@@ -2744,41 +2740,41 @@ module Crystal
     def initialize(@program, @base_type)
     end
 
-    def check_method_missing(name, arg_types, block)
+    def check_method_missing(signature)
       method_missing = base_type.lookup_method_missing
       defined = false
       if method_missing
-        defined = base_type.define_method_from_method_missing(method_missing, name, arg_types, block) || defined
+        defined = base_type.define_method_from_method_missing(method_missing, signature) || defined
       end
 
-      defined = add_subclasses_method_missing_matches(base_type, method_missing, name, arg_types, block) || defined
+      defined = add_subclasses_method_missing_matches(base_type, method_missing, signature) || defined
       defined
     end
 
-    def add_subclasses_method_missing_matches(base_type, method_missing, name, arg_types, block)
+    def add_subclasses_method_missing_matches(base_type, method_missing, signature)
       defined = false
 
       base_type.subclasses.each do |subclass|
         subclass = subclass as DefContainer
 
         # First check if we can find the method
-        existing_def = subclass.lookup_first_def(name, block)
+        existing_def = subclass.lookup_first_def(signature.name, signature.block)
         next if existing_def
 
         subclass_method_missing = subclass.lookup_method_missing
 
         # Check if the subclass redefined the method_missing
         if subclass_method_missing && subclass_method_missing.object_id != method_missing.object_id
-          subclass.define_method_from_method_missing(subclass_method_missing, name, arg_types, block)
+          subclass.define_method_from_method_missing(subclass_method_missing, signature)
           defined = true
         elsif method_missing
           # Otherwise, we need to define this method missing because of macro vars like @name
-          subclass.define_method_from_method_missing(method_missing, name, arg_types, block)
+          subclass.define_method_from_method_missing(method_missing, signature)
           subclass_method_missing = method_missing
           defined = true
         end
 
-        defined = add_subclasses_method_missing_matches(subclass, subclass_method_missing, name, arg_types, block) || defined
+        defined = add_subclasses_method_missing_matches(subclass, subclass_method_missing, signature) || defined
       end
 
       defined