@@ -44,6 +44,7 @@ module SigApplicator
4444
4545 class << self
4646 def apply_all!
47+ @mutex . synchronize { @type_errors . clear }
4748 configure_error_handler!
4849 register_summary_hook!
4950
@@ -145,14 +146,12 @@ def apply_scope(node, errors)
145146 return [ 0 , 0 ] if SKIP_PREFIXES . any? { |prefix | class_name . start_with? ( prefix ) }
146147 return [ 0 , 0 ] if SKIP_CLASSES . include? ( class_name )
147148
148- missing_name = false
149- klass = begin
150- Object . const_get ( class_name )
149+ begin
150+ klass = Object . const_get ( class_name )
151151 rescue NameError
152152 errors << "#{ class_name } : class not found"
153- missing_name = true
153+ return [ 0 , 0 ]
154154 end
155- return [ 0 , 0 ] if missing_name
156155
157156 applied = 0
158157 skipped = 0
@@ -192,47 +191,19 @@ def apply_method_sig(target, class_name, method_node, errors, class_method: fals
192191
193192 return :skipped if SKIP_METHODS . include? ( full_name )
194193
195- missing_method = false
196- original = begin
197- target . instance_method ( method_name )
194+ begin
195+ original = target . instance_method ( method_name )
198196 rescue NameError
199197 errors << "#{ full_name } : method not found"
200- missing_method = true
198+ return false
201199 end
202- return false if missing_method
203200
204- # Skip when there's a block param mismatch between the sig and actual
205- # method. Common causes:
206- # - Anonymous block forwarding (Ruby 3.1+ `def foo(&)`)
207- # - Methods using yield with no block param
208- # - Sigs that omit block params the method declares
209- actual_params = original . parameters
210- actual_block = actual_params . find { |kind , _ | kind == :block }
211- sig_block_params = method_node . sigs . flat_map { |sig | sig . params . select { |p | p . type &.include? ( 'T.proc' ) } }
212- actual_has_block = !actual_block . nil?
213- actual_block_anonymous = actual_block && ( actual_block [ 1 ] . nil? || actual_block [ 1 ] == :& )
214- sig_has_block = sig_block_params . any?
215- return :skipped if actual_block_anonymous && sig_has_block
216- return :skipped if actual_has_block && !sig_has_block
217- return :skipped if !actual_has_block && sig_has_block
218-
219- # Skip setter methods where Ruby creates unnamed params (attr_writer)
220- has_unnamed_params = actual_params . any? { |_kind , name | name . nil? }
221- return :skipped if has_unnamed_params && method_name . end_with? ( '=' )
222-
223- # Skip when the actual method only has rest/unnamed params but the sig
224- # declares specific named params. This happens with synthetic methods
225- # (e.g., Data.define generates .new, .[], #initialize, #with with a
226- # single splat) where the RBI provides typed keyword params for better
227- # static checking but the runtime signature is incompatible.
228- non_block_params = actual_params . reject { |kind , _ | kind == :block } # rubocop:disable Style/HashExcept
229- all_rest_or_unnamed = non_block_params . all? { |kind , _ | kind == :rest || kind == :keyrest }
230- sig_named_params = method_node . sigs . flat_map { |s | s . params . reject { |p | p . type &.include? ( 'T.proc' ) } }
231- return :skipped if all_rest_or_unnamed && non_block_params . any? && sig_named_params . any?
201+ return :skipped if skip_method? ( original , method_node , method_name )
232202
233203 target . extend ( T ::Sig )
234204
235205 method_node . sigs . each do |sig |
206+ # RBI::Sig#string serializes back to valid T::Sig DSL source
236207 sig_source = sig . string
237208 begin
238209 target . class_eval ( sig_source )
@@ -252,5 +223,35 @@ def apply_method_sig(target, class_name, method_node, errors, class_method: fals
252223
253224 true
254225 end
226+
227+ # Determines whether a method should be skipped for sig application based
228+ # on parameter shape mismatches between the RBI sig and the actual method.
229+ def skip_method? ( original , method_node , method_name )
230+ actual_params = original . parameters
231+
232+ # Block param mismatch: anonymous block forwarding (Ruby 3.1+ `def foo(&)`),
233+ # methods using yield with no block param, or sigs that omit declared blocks.
234+ actual_block = actual_params . find { |kind , _ | kind == :block }
235+ sig_block_params = method_node . sigs . flat_map { |sig | sig . params . select { |p | p . type &.include? ( 'T.proc' ) } }
236+ actual_has_block = !actual_block . nil?
237+ actual_block_anonymous = actual_block && ( actual_block [ 1 ] . nil? || actual_block [ 1 ] == :& )
238+ sig_has_block = sig_block_params . any?
239+ return true if actual_block_anonymous && sig_has_block
240+ return true if actual_has_block != sig_has_block
241+
242+ # Setter methods where Ruby creates unnamed params (attr_writer)
243+ has_unnamed_params = actual_params . any? { |_kind , name | name . nil? }
244+ return true if has_unnamed_params && method_name . end_with? ( '=' )
245+
246+ # Synthetic methods (e.g., Data.define generates .new, .[], #initialize,
247+ # #with with a single splat) where the RBI provides typed keyword params
248+ # for better static checking but the runtime signature is incompatible.
249+ non_block_params = actual_params . reject { |kind , _ | kind == :block } # rubocop:disable Style/HashExcept
250+ all_rest_or_unnamed = non_block_params . all? { |kind , _ | kind == :rest || kind == :keyrest }
251+ sig_named_params = method_node . sigs . flat_map { |s | s . params . reject { |p | p . type &.include? ( 'T.proc' ) } }
252+ return true if all_rest_or_unnamed && non_block_params . any? && sig_named_params . any?
253+
254+ false
255+ end
255256 end
256257end
0 commit comments