Module: Docscribe::Validator::GenericCompatibility
- Defined in:
- lib/docscribe/validator/generic_compatibility.rb
Overview
Service object answering generic_compatible? without hardcoding alias names.
Dynamically decides if two type strings are compatible via generics/aliases. Uses hash dispatch to avoid long if/else chains and to allow easy extension.
Constant Summary collapse
- CHECKS =
Check registry: name => checker method symbol
{ generic_base: :generic_base_compatible?, short_name: :short_name_compatible?, alias_hash: :alias_hash_compatible?, tuple_array: :tuple_array_compatible?, optional_nil: :optional_nil_compatible?, union_containment: :union_containment?, optional_suffix: :optional_suffix_compatible?, generic_inner_alias: :generic_inner_alias_compatible?, bare_alias: :bare_alias_compatible?, union_optional: :union_vs_optional_compatible?, object_compatible: :object_compatible?, fallback_union: :fallback_union_check? }.freeze
Class Method Summary collapse
-
.alias_hash_compatible?(yard_type, expected_type) ⇒ Boolean
Whether alias (lowercase after ::) vs Hash/Array/Range is compatible.
- .alias_hash_pair?(alias_type, hash_type) ⇒ Boolean
-
.alias_token?(token) ⇒ Boolean
Whether token looks like an alias (dynamic, not hardcoded Elem/U).
-
.bare_alias?(normalized) ⇒ Boolean
Whether normalized string is a bare alias token (no generics, no union).
-
.bare_alias_compatible?(yard_type, expected_type) ⇒ Boolean
Whether one side is a bare alias (e.g., V, U, T, Elem) vs concrete type.
-
.bare_single_cap?(normalized) ⇒ Boolean
Whether normalized string is a bare single-capital alias (V, U, T, K, Elem? no, single A-Z only).
-
.base_name(type_str) ⇒ String
Base name before generic or paren.
- .canonical_without_nil(canonical) ⇒ Array<String>
- .canonicalize_parts(parts) ⇒ Array<String>
-
.comma_nil_pair?(norm_yard, norm_expected) ⇒ Boolean
Whether one side is "nil" and the other contains ", nil" union.
-
.compatible?(yard_type, expected_type, fallback_type: 'Object', method_name: nil) ⇒ Boolean
Whether two type strings are generic compatible (any checker true).
- .empty_str?(str) ⇒ Boolean
- .expand_optional_parts(parts) ⇒ Array<String>
-
.extract_inner(normalized) ⇒ String?
Extracts inner generic arguments from normalized Array/Hash type.
-
.fallback_union?(type_str, fallback) ⇒ Boolean
Whether a type string contains only the fallback type (comma-separated, ignoring trailing
?). -
.fallback_union_check?(yard_type, expected_type, fallback_type) ⇒ Boolean
Whether either type is a fallback-only union for the given fallback type.
-
.generic_base_compatible?(yard_type, expected_type) ⇒ Boolean
Whether generic base matches: Hash vs Hash<Symbol,String> or Array vs Array
. -
.generic_inner_alias_compatible?(yard_type, expected_type) ⇒ Boolean
Whether Array/Hash generic inners contain alias tokens.
-
.generic_pair?(norm_yard, norm_expected) ⇒ Boolean
Whether both normalized types are Array or Hash generics.
-
.generic_string?(normalized) ⇒ Boolean
Whether a normalized type string contains generic brackets.
- .handle_split_char(chr, state) ⇒ void
- .handle_split_comma(state) ⇒ void
-
.inner_has_alias?(inner) ⇒ Boolean
Whether any comma-separated part of generic inner is an alias token.
-
.node_nil_pair?(norm_yard, norm_expected) ⇒ Boolean
Whether Parser::AST::Node vs nil is considered compatible.
-
.normalize(type_str) ⇒ String
Normalizes type string for comparison.
- .normalized_union_str(str) ⇒ String
-
.object_compatible?(yard_type, expected_type) ⇒ Boolean
Whether Object supertype compatibility holds: e.g., String vs Object, String, nil vs Object, nil.
- .object_supertype_pair?(yard_without_nil, exp_without_nil) ⇒ Boolean
-
.optional_canonical_parts(str) ⇒ Array<String>
Canonical optional parts for
T?/T, nil/T|nil/Tforms. -
.optional_forms_equal?(first, second) ⇒ Boolean
Whether optional forms
T?,T, nil,T|nilare equivalent (same canonical parts). -
.optional_nil_compatible?(yard_type, expected_type) ⇒ Boolean
Whether optional vs nil pair is compatible.
-
.optional_suffix_compatible?(yard_type, expected_type) ⇒ Boolean
Whether types match after stripping trailing "?".
- .part_compatible_with_single?(single, part, fallback_type) ⇒ Boolean
-
.pipe_aware_suffix_vs_union_first?(yard_type, expected_type) ⇒ Boolean
Pipe-aware variant of #suffix_vs_union_first? normalizing
|to,. -
.question_nil_pair?(norm_yard, norm_expected) ⇒ Boolean
Whether one side is "nil" and the other uses trailing "?" optional syntax.
-
.question_vs_comma_nil?(norm_yard, norm_expected) ⇒ Boolean
Method documentation.
-
.short_compatible?(short_yard, short_expected, norm_yard, norm_expected) ⇒ Boolean
Whether short names are compatible given full normalized forms.
-
.short_name(type_str) ⇒ String
Short name without namespace or generic args.
-
.short_name_compatible?(yard_type, expected_type) ⇒ Boolean
Whether short names equal: Docscribe::Config vs Config, Parser::Source::Range vs Range.
- .single_optional_form?(str) ⇒ Boolean
- .single_optional_parts(str) ⇒ Array<String>
- .single_vs_union?(single_parts, union_parts, fallback_type) ⇒ Boolean
-
.split_top_level_commas_local(str) ⇒ Array<String>
Split by top-level commas outside
< > [ ] ( )(generic-aware). -
.suffix_vs_union_first?(yard_type, expected_type) ⇒ Boolean
Method documentation.
-
.tuple_array_compatible?(yard_type, expected_type) ⇒ Boolean
Whether tuple "(String, Integer)" vs Array is compatible.
-
.union_containment?(yard_type, expected_type) ⇒ Boolean
Whether one type is contained in the other's comma-separated union.
- .union_parts(str) ⇒ Array<String>
- .union_parts_compatible?(yard_type, expected_type, fallback_type, _method_name) ⇒ Boolean
-
.union_vs_optional_compatible?(yard_type, expected_type) ⇒ Boolean
Whether union
?, nilforms are compatible:Object?vsObject, nilvsObject|nil. - .update_split_depth(chr, state) ⇒ void
-
.void_compatible?(yard_type, expected_type, fallback_type = 'Object', method_name: nil) ⇒ Boolean
Whether void YARD type is compatible with expected type.
- .void_fallback_or_nil?(expected_type, fallback_type) ⇒ Boolean
- .void_initialize_compatible?(expected_type, method_name) ⇒ Boolean
- .void_predicate_compatible?(expected_type, method_name) ⇒ Boolean
Class Method Details
.alias_hash_compatible?(yard_type, expected_type) ⇒ Boolean
Whether alias (lowercase after ::) vs Hash/Array/Range is compatible.
Checks if one side is a namespaced alias starting with lowercase and the other is Hash, Array, or Range.
248 249 250 251 252 253 254 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 248 def alias_hash_compatible?(yard_type, expected_type) norm_yard = normalize(yard_type) norm_expected = normalize(expected_type) [[norm_yard, norm_expected], [norm_expected, norm_yard]].any? do |alias_type, hash_type| alias_hash_pair?(alias_type, hash_type) end end |
.alias_hash_pair?(alias_type, hash_type) ⇒ Boolean
259 260 261 262 263 264 265 266 267 268 269 270 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 259 def alias_hash_pair?(alias_type, hash_type) base = base_name(hash_type) return false unless %w[Hash Array Range].include?(base) || hash_type.start_with?('Hash') || hash_type.start_with?('Array') || hash_type == 'Range' short_alias = alias_type.split('::').last.to_s return false if %w[node Node].include?(short_alias) short_alias =~ /\A[a-z]/ && alias_type.include?('::') end |
.alias_token?(token) ⇒ Boolean
Whether token looks like an alias (dynamic, not hardcoded Elem/U).
Any token that is not a known Ruby/YARD primitive is considered alias.
E.g., Elem, U, my_alias, Foo::Bar, change vs String, Integer, Hash.
424 425 426 427 428 429 430 431 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 424 def alias_token?(token) # rubocop:disable SortedMethodsByCall/Waterfall base = token.split('<').first.split('[').first.strip.delete_suffix('?').strip return false if Docscribe::Types::Primitive.primitive?(base) return true if base =~ /\A[a-z]/ || base.include?('::') return true if base =~ /\A[A-Z]\z/ !!(base =~ /\A[A-Z][A-Za-z0-9_]*\z/) end |
.bare_alias?(normalized) ⇒ Boolean
Whether normalized string is a bare alias token (no generics, no union).
469 470 471 472 473 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 469 def (normalized) return false if normalized.include?('<') || normalized.include?('[') || normalized.include?(',') alias_token?(normalized.strip) end |
.bare_alias_compatible?(yard_type, expected_type) ⇒ Boolean
Whether one side is a bare alias (e.g., V, U, T, Elem) vs concrete type.
Bare alias means single token without generic brackets or commas that
satisfies #alias_token?. E.g., "V" vs "Array
442 443 444 445 446 447 448 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 442 def (yard_type, expected_type) norm_yard = normalize(yard_type) norm_expected = normalize(expected_type) return false if norm_yard.empty? || norm_expected.empty? (norm_yard) || (norm_expected) end |
.bare_single_cap?(normalized) ⇒ Boolean
Whether normalized string is a bare single-capital alias (V, U, T, K, Elem? no, single A-Z only).
Single capital letters are RBS type parameters (generic placeholders) that should
be compatible with concrete types like Array
458 459 460 461 462 463 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 458 def (normalized) return false if normalized.include?('<') || normalized.include?('[') || normalized.include?(',') stripped = normalized.strip.delete_suffix('?').strip stripped =~ /\A[A-Z]\z/ && alias_token?(stripped) end |
.base_name(type_str) ⇒ String
Base name before generic or paren.
Strips "<", "[", "(" suffixes. E.g., "Hash<Symbol,String>" => "Hash".
278 279 280 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 278 def base_name(type_str) normalize(type_str).split('<').first.split('[').first.split('(').first.strip end |
.canonical_without_nil(canonical) ⇒ Array<String>
555 556 557 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 555 def canonical_without_nil(canonical) canonical.reject { |p| p == 'nil' } end |
.canonicalize_parts(parts) ⇒ Array<String>
592 593 594 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 592 def canonicalize_parts(parts) (parts).map { |part| normalize(part) }.reject(&:empty?).uniq.sort end |
.comma_nil_pair?(norm_yard, norm_expected) ⇒ Boolean
Whether one side is "nil" and the other contains ", nil" union.
E.g., "nil" vs "String, nil" is considered compatible.
329 330 331 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 329 def comma_nil_pair?(norm_yard, norm_expected) (norm_yard.include?(', nil') && norm_expected == 'nil') || (norm_expected.include?(', nil') && norm_yard == 'nil') end |
.compatible?(yard_type, expected_type, fallback_type: 'Object', method_name: nil) ⇒ Boolean
Whether two type strings are generic compatible (any checker true).
37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 37 def compatible?(yard_type, expected_type, fallback_type: 'Object', method_name: nil) return true if void_compatible?(yard_type, expected_type, fallback_type, method_name: method_name) return true if union_parts_compatible?(yard_type, expected_type, fallback_type, method_name) CHECKS.any? do |name, checker| if name == :fallback_union send(checker, yard_type, expected_type, fallback_type) else send(checker, yard_type, expected_type) end end end |
.empty_str?(str) ⇒ Boolean
574 575 576 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 574 def empty_str?(str) str.to_s.strip.empty? end |
.expand_optional_parts(parts) ⇒ Array<String>
605 606 607 608 609 610 611 612 613 614 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 605 def (parts) parts.flat_map do |part| stripped = part.strip if stripped.end_with?('?') [normalize(stripped.delete_suffix('?').strip), 'nil'] else [normalize(stripped)] end end end |
.extract_inner(normalized) ⇒ String?
Extracts inner generic arguments from normalized Array/Hash type.
E.g., "Array
405 406 407 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 405 def extract_inner(normalized) normalized[/\A(?:Array|Hash)[<\[](.*)[>\]]\z/, 1] end |
.fallback_union?(type_str, fallback) ⇒ Boolean
Whether a type string contains only the fallback type (comma-separated, ignoring trailing ?).
164 165 166 167 168 169 170 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 164 def fallback_union?(type_str, fallback) return false if type_str.nil? || type_str.strip.empty? fallback_norm = normalize(fallback) parts = type_str.to_s.split(',').map { |part| normalize(part.strip.delete_suffix('?').strip) } parts.all? { |part| part == fallback_norm || part.empty? } end |
.fallback_union_check?(yard_type, expected_type, fallback_type) ⇒ Boolean
Whether either type is a fallback-only union for the given fallback type.
Delegates to #fallback_union? for both yard_type and expected_type.
155 156 157 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 155 def fallback_union_check?(yard_type, expected_type, fallback_type) fallback_union?(yard_type, fallback_type) || fallback_union?(expected_type, fallback_type) end |
.generic_base_compatible?(yard_type, expected_type) ⇒ Boolean
Whether generic base matches: Hash vs Hash<Symbol,String> or Array vs Array
True when normalized types equal or one is bare base of the other's generic.
179 180 181 182 183 184 185 186 187 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 179 def generic_base_compatible?(yard_type, expected_type) norm_yard = normalize(yard_type) norm_expected = normalize(expected_type) return true if norm_yard == norm_expected yard_generic = norm_expected !~ /[<\[]/ && (norm_yard.start_with?("#{norm_expected}<") || norm_yard.start_with?("#{norm_expected}[")) expected_generic = norm_yard !~ /[<\[]/ && (norm_expected.start_with?("#{norm_yard}<") || norm_expected.start_with?("#{norm_yard}[")) yard_generic || expected_generic end |
.generic_inner_alias_compatible?(yard_type, expected_type) ⇒ Boolean
Whether Array/Hash generic inners contain alias tokens.
True when both are Array/Hash generics and either inner contains an alias (lowercase or "::").
E.g., "Array<my_alias>" vs "Array
378 379 380 381 382 383 384 385 386 387 388 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 378 def generic_inner_alias_compatible?(yard_type, expected_type) norm_yard = normalize(yard_type) norm_expected = normalize(expected_type) return false unless generic_pair?(norm_yard, norm_expected) inner_yard = extract_inner(norm_yard) inner_expected = extract_inner(norm_expected) return false unless inner_yard && inner_expected inner_has_alias?(inner_yard) || inner_has_alias?(inner_expected) end |
.generic_pair?(norm_yard, norm_expected) ⇒ Boolean
Whether both normalized types are Array or Hash generics.
395 396 397 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 395 def generic_pair?(norm_yard, norm_expected) norm_yard =~ /\A(?:Array|Hash)[<\[]/ && norm_expected =~ /\A(?:Array|Hash)[<\[]/ end |
.generic_string?(normalized) ⇒ Boolean
Whether a normalized type string contains generic brackets.
220 221 222 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 220 def generic_string?(normalized) normalized.include?('<') || normalized.include?('[') end |
.handle_split_char(chr, state) ⇒ void
This method returns an undefined value.
632 633 634 635 636 637 638 639 640 641 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 632 def handle_split_char(chr, state) case chr when '<', '>', '[', ']', '(', ')' update_split_depth(chr, state) when ',' handle_split_comma(state) else state[:cur] << chr end end |
.handle_split_comma(state) ⇒ void
This method returns an undefined value.
655 656 657 658 659 660 661 662 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 655 def handle_split_comma(state) if state[:da].zero? && state[:db].zero? && state[:dp].zero? state[:parts] << state[:cur] state[:cur] = +'' else state[:cur] << ',' end end |
.inner_has_alias?(inner) ⇒ Boolean
Whether any comma-separated part of generic inner is an alias token.
413 414 415 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 413 def inner_has_alias?(inner) inner.split(',').any? { |part| alias_token?(part.strip) } end |
.node_nil_pair?(norm_yard, norm_expected) ⇒ Boolean
Whether Parser::AST::Node vs nil is considered compatible.
Special-case for AST nodes where nil represents absent node.
340 341 342 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 340 def node_nil_pair?(norm_yard, norm_expected) (norm_yard == 'Parser::AST::Node' && norm_expected == 'nil') || (norm_expected == 'Parser::AST::Node' && norm_yard == 'nil') end |
.normalize(type_str) ⇒ String
Normalizes type string for comparison.
Strips, squeezes spaces, converts "["/"]" to "<"/">", replaces "untyped"/"FALLBACK_TYPE" with "Object".
690 691 692 693 694 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 690 def normalize(type_str) s = type_str.to_s s = s.sub(/#.*\z/m, '').strip unless s.lstrip.start_with?('#') s.strip.squeeze(' ').gsub('[', '<').gsub(']', '>').gsub(/\buntyped\b/, 'Object').gsub(/\bFALLBACK_TYPE\b/, 'Object') end |
.normalized_union_str(str) ⇒ String
580 581 582 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 580 def normalized_union_str(str) str.to_s.gsub('|', ',').strip end |
.object_compatible?(yard_type, expected_type) ⇒ Boolean
Whether Object supertype compatibility holds: e.g., String vs Object, String, nil vs Object, nil.
If expected is Object (or Object, nil, Object? etc) and yard is a concrete type
(String, Array
533 534 535 536 537 538 539 540 541 542 543 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 533 def object_compatible?(yard_type, expected_type) yard_canonical = optional_canonical_parts(yard_type) exp_canonical = optional_canonical_parts(expected_type) return false if yard_canonical.empty? || exp_canonical.empty? yard_without_nil = canonical_without_nil(yard_canonical) exp_without_nil = canonical_without_nil(exp_canonical) return false unless object_supertype_pair?(yard_without_nil, exp_without_nil) yard_canonical.include?('nil') == exp_canonical.include?('nil') end |
.object_supertype_pair?(yard_without_nil, exp_without_nil) ⇒ Boolean
548 549 550 551 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 548 def object_supertype_pair?(yard_without_nil, exp_without_nil) (exp_without_nil == ['Object'] && yard_without_nil != ['Object'] && !yard_without_nil.empty?) || (yard_without_nil == ['Object'] && exp_without_nil != ['Object'] && !exp_without_nil.empty?) end |
.optional_canonical_parts(str) ⇒ Array<String>
Canonical optional parts for T? / T, nil / T|nil / T forms.
563 564 565 566 567 568 569 570 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 563 def optional_canonical_parts(str) return [] if str.nil? || empty_str?(str) s = normalized_union_str(str) return single_optional_parts(s) if single_optional_form?(s) canonicalize_parts(split_top_level_commas_local(s)) end |
.optional_forms_equal?(first, second) ⇒ Boolean
Whether optional forms T?, T, nil, T|nil are equivalent (same canonical parts).
Canonicalizes T? => [T, nil], T, nil / T|nil => [T, nil], T => [T].
Generic-aware split keeps Hash<String, Integer>, nil intact.
517 518 519 520 521 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 517 def optional_forms_equal?(first, second) pa = optional_canonical_parts(first) pb = optional_canonical_parts(second) !pa.empty? && pa == pb end |
.optional_nil_compatible?(yard_type, expected_type) ⇒ Boolean
Whether optional vs nil pair is compatible.
Delegates to #question_nil_pair?, #comma_nil_pair?, and #node_nil_pair?.
302 303 304 305 306 307 308 309 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 302 def optional_nil_compatible?(yard_type, expected_type) norm_yard = normalize(yard_type) norm_expected = normalize(expected_type) return true if question_nil_pair?(norm_yard, norm_expected) return true if comma_nil_pair?(norm_yard, norm_expected) node_nil_pair?(norm_yard, norm_expected) end |
.optional_suffix_compatible?(yard_type, expected_type) ⇒ Boolean
Whether types match after stripping trailing "?".
E.g., "String" vs "String?" considered compatible.
366 367 368 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 366 def optional_suffix_compatible?(yard_type, expected_type) normalize(yard_type).delete_suffix('?') == normalize(expected_type).delete_suffix('?') end |
.part_compatible_with_single?(single, part, fallback_type) ⇒ Boolean
91 92 93 94 95 96 97 98 99 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 91 def part_compatible_with_single?(single, part, fallback_type) CHECKS.any? do |name, checker| next if %i[union_containment fallback_union].include?(name) compatible = name == :fallback_union ? send(checker, single, part, fallback_type) : send(checker, single, part) return true if compatible || single == part end false end |
.pipe_aware_suffix_vs_union_first?(yard_type, expected_type) ⇒ Boolean
Pipe-aware variant of #suffix_vs_union_first? normalizing | to ,.
498 499 500 501 502 503 504 505 506 507 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 498 def pipe_aware_suffix_vs_union_first?(yard_type, expected_type) # Normalize both by converting pipe to comma before comparison y = yard_type.to_s.gsub('|', ',') e = expected_type.to_s.gsub('|', ',') left_match = normalize(y).delete_suffix('?') == normalize(split_top_level_commas_local(e).first || '') right_match = normalize(e).delete_suffix('?') == normalize(split_top_level_commas_local(y).first || '') left_match || right_match end |
.question_nil_pair?(norm_yard, norm_expected) ⇒ Boolean
Whether one side is "nil" and the other uses trailing "?" optional syntax.
E.g., "nil" vs "String?" is considered compatible.
318 319 320 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 318 def question_nil_pair?(norm_yard, norm_expected) (norm_yard == 'nil' && norm_expected.end_with?('?')) || (norm_expected == 'nil' && norm_yard.end_with?('?')) end |
.question_vs_comma_nil?(norm_yard, norm_expected) ⇒ Boolean
Method documentation.
669 670 671 672 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 669 def question_vs_comma_nil?(norm_yard, norm_expected) norm_yard.delete(' ') == "#{norm_expected.delete(' ').delete_suffix('?')},nil" || norm_expected.delete(' ') == "#{norm_yard.delete(' ').delete_suffix('?')},nil" end |
.short_compatible?(short_yard, short_expected, norm_yard, norm_expected) ⇒ Boolean
Whether short names are compatible given full normalized forms.
Allows Docscribe::Config vs Config and cross-checks short vs full forms.
233 234 235 236 237 238 239 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 233 def short_compatible?(short_yard, short_expected, norm_yard, norm_expected) return true if short_yard == short_expected && short_yard != norm_yard && short_expected != norm_expected return true if short_yard == norm_expected return true if short_expected == norm_yard false end |
.short_name(type_str) ⇒ String
Short name without namespace or generic args.
Strips module prefix and generic suffix. E.g., "Docscribe::Config
212 213 214 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 212 def short_name(type_str) normalize(type_str).split('::').last.to_s.split('<').first.split('[').first.strip end |
.short_name_compatible?(yard_type, expected_type) ⇒ Boolean
Whether short names equal: Docscribe::Config vs Config, Parser::Source::Range vs Range.
Ignores generic brackets and checks namespace-elided compatibility via #short_compatible?.
196 197 198 199 200 201 202 203 204 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 196 def short_name_compatible?(yard_type, expected_type) norm_yard = normalize(yard_type) norm_expected = normalize(expected_type) return false if generic_string?(norm_yard) || generic_string?(norm_expected) short_yard = short_name(norm_yard) short_expected = short_name(norm_expected) short_compatible?(short_yard, short_expected, norm_yard, norm_expected) end |
.single_optional_form?(str) ⇒ Boolean
586 587 588 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 586 def single_optional_form?(str) str.end_with?('?') && !str.include?(',') end |
.single_optional_parts(str) ⇒ Array<String>
598 599 600 601 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 598 def single_optional_parts(str) base = normalize(str.delete_suffix('?').strip) [base, 'nil'].sort end |
.single_vs_union?(single_parts, union_parts, fallback_type) ⇒ Boolean
80 81 82 83 84 85 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 80 def single_vs_union?(single_parts, union_parts, fallback_type) return false unless single_parts.size == 1 && union_parts.size > 1 single = single_parts.first union_parts.any? { |part| part_compatible_with_single?(single, part, fallback_type) } end |
.split_top_level_commas_local(str) ⇒ Array<String>
Split by top-level commas outside < > [ ] ( ) (generic-aware).
Mirrors Docscribe::Infer::Returns.split_top_level_commas but local to avoid cross-dep.
622 623 624 625 626 627 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 622 def split_top_level_commas_local(str) state = { parts: [], cur: +'', da: 0, db: 0, dp: 0 } #: Hash[Symbol, untyped] str.each_char { |chr| handle_split_char(chr, state) } state[:parts] << state[:cur] unless state[:cur].empty? state[:parts] end |
.suffix_vs_union_first?(yard_type, expected_type) ⇒ Boolean
Method documentation.
679 680 681 682 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 679 def suffix_vs_union_first?(yard_type, expected_type) normalize(yard_type).delete_suffix('?') == normalize(expected_type).split(',').first&.strip || normalize(expected_type).delete_suffix('?') == normalize(yard_type).split(',').first&.strip end |
.tuple_array_compatible?(yard_type, expected_type) ⇒ Boolean
Whether tuple "(String, Integer)" vs Array is compatible.
True when one side is bare "Array" and the other is parenthesized tuple.
289 290 291 292 293 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 289 def tuple_array_compatible?(yard_type, expected_type) norm_yard = normalize(yard_type) norm_expected = normalize(expected_type) (norm_expected == 'Array' && norm_yard =~ /\A\(.*\)\z/) || (norm_yard == 'Array' && norm_expected =~ /\A\(.*\)\z/) end |
.union_containment?(yard_type, expected_type) ⇒ Boolean
Whether one type is contained in the other's comma-separated union.
Checks both directions after normalization (e.g., "String" in "String, Integer").
351 352 353 354 355 356 357 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 351 def union_containment?(yard_type, expected_type) return false if yard_type.nil? || expected_type.nil? norm_yard = normalize(yard_type) expected_type.split(',').any? { |part| normalize(part) == norm_yard } || yard_type.split(',').any? { |part| normalize(part) == normalize(expected_type) } end |
.union_parts(str) ⇒ Array<String>
72 73 74 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 72 def union_parts(str) str.include?(',') ? split_top_level_commas_local(str).map { |p| normalize(p) } : [normalize(str)] end |
.union_parts_compatible?(yard_type, expected_type, fallback_type, _method_name) ⇒ Boolean
56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 56 def union_parts_compatible?(yard_type, expected_type, fallback_type, _method_name) yt = yard_type.to_s et = expected_type.to_s return false unless yt.include?(',') || et.include?(',') parts_yard = union_parts(yt) parts_expected = union_parts(et) return true if single_vs_union?(parts_yard, parts_expected, fallback_type) return true if single_vs_union?(parts_expected, parts_yard, fallback_type) false end |
.union_vs_optional_compatible?(yard_type, expected_type) ⇒ Boolean
Whether union ?, nil forms are compatible: Object? vs Object, nil vs Object|nil.
Handles all three forms SomeType? == SomeType|nil == SomeType, nil plus
bare SomeType vs optional equivalence via pipe/comma normalization.
483 484 485 486 487 488 489 490 491 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 483 def union_vs_optional_compatible?(yard_type, expected_type) norm_yard = normalize(yard_type).gsub('|', ',') norm_expected = normalize(expected_type).gsub('|', ',') return true if question_vs_comma_nil?(norm_yard, norm_expected) return true if suffix_vs_union_first?(yard_type, expected_type) return true if pipe_aware_suffix_vs_union_first?(yard_type, expected_type) optional_forms_equal?(yard_type, expected_type) end |
.update_split_depth(chr, state) ⇒ void
This method returns an undefined value.
646 647 648 649 650 651 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 646 def update_split_depth(chr, state) deltas = { '<' => [:da, 1], '>' => [:da, -1], '[' => [:db, 1], ']' => [:db, -1], '(' => [:dp, 1], ')' => [:dp, -1] } key, delta = deltas[chr] state[key] += delta if key state[:cur] << chr end |
.void_compatible?(yard_type, expected_type, fallback_type = 'Object', method_name: nil) ⇒ Boolean
Whether void YARD type is compatible with expected type.
Handles fallback unions and dynamic initialize/setup compatibility
where void is treated as compatible with Hash, self, or Boolean
for initializers per Ruby idiom.
112 113 114 115 116 117 118 119 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 112 def void_compatible?(yard_type, expected_type, fallback_type = 'Object', method_name: nil) return false unless normalize(yard_type) == 'void' return true if void_fallback_or_nil?(expected_type, fallback_type) return true if void_initialize_compatible?(expected_type, method_name) return true if void_predicate_compatible?(expected_type, method_name) false end |
.void_fallback_or_nil?(expected_type, fallback_type) ⇒ Boolean
124 125 126 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 124 def void_fallback_or_nil?(expected_type, fallback_type) fallback_union?(expected_type, fallback_type) || %w[nil void].include?(normalize(expected_type)) end |
.void_initialize_compatible?(expected_type, method_name) ⇒ Boolean
131 132 133 134 135 136 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 131 def void_initialize_compatible?(expected_type, method_name) return false unless method_name.to_s =~ /initialize|setup/ norm = normalize(expected_type).delete_suffix('?').strip norm == 'Hash' || norm.start_with?('Hash<') || norm.start_with?('Hash[') || %w[self Boolean].include?(norm) end |
.void_predicate_compatible?(expected_type, method_name) ⇒ Boolean
141 142 143 144 145 |
# File 'lib/docscribe/validator/generic_compatibility.rb', line 141 def void_predicate_compatible?(expected_type, method_name) return false unless method_name.to_s.end_with?('?') normalize(expected_type).delete_suffix('?').strip == 'Boolean' end |