Skip to content

Answer type questions from every union member - #345

Draft
apiology wants to merge 4 commits into
masterfrom
restrict-type-interface
Draft

apiology wants to merge 4 commits into
masterfrom
restrict-type-interface

Conversation

@apiology

Copy link
Copy Markdown
Owner

Claude: This PR was written by Claude Code on behalf of @apiology.

Problem: Code outside the type classes answers for a whole union from its first member:

# => "(String | Class)"  (the <Foo> is dropped)
Pin::ProxyType.new(name: 'foo', return_type: ComplexType.parse('String, Class<Foo>')).type_desc
# => false
ComplexType.parse('Foo', 'void').void?

The 20 sites in castwide#1382 each take a type apart themselves, and several already give wrong answers for plain unions.

Solution: ComplexType and UniqueType now share the restricted interface from castwide#1382 (satisfied_by?, candidate_methods_from, method_stack_pins, qualify_parts, combine_via, order_nil_last and the rest), the Enumerable-style methods are removed, and every caller in lib/ uses the interface, so each question is answered by the type across all its members.

ApiMap#get_complex_type_methods is removed in favour of type.candidate_methods_from(api_map, context, internal). Intersection types from castwide#1231 are not included here.

Code outside the type classes took types apart itself: it counted
.items, flattened members with flat_map(&:items), read the first
member's tag or generic? as the whole type's, and walked members to
find methods (castwide#1382). Each of those sites makes an
assumption a union or a later type kind can break.

Give both classes the restricted interface from castwide#1382 instead:
satisfied_by?, conforms_to_unique?, provides_duck_method?,
candidate_methods_from, any_generic?, unalias_and_qualify,
method_stack_pins, qualify_parts, order_nil_last, combine_via (with
TypeMethods.combine_members), reduce_object and reduce_class_type per
member, plus the union-only unioned_items, ComplexType.union and
ComplexType.flatten_unions. A union is now void or undefined when any
member is. Union members dedup by rooted_tags, and the Boolean fold
moves into fold_boolean_cases.

Remove the Enumerable-style surface (map, each, each_unique_type,
first, length, to_a, [], select, all?, any?, bottom? on ComplexType;
map, each, all?, any?, to_a on UniqueType) and switch every caller in
lib/ to the interface. ApiMap#get_complex_type_methods becomes
#candidate_methods_from on the type, with ApiMap#visibility_for left
behind for the ancestry check. An unknown name sent to a ComplexType
now reaches super and raises even when the union is empty.

Ported from castwide#1231 without its intersection type.
Exercise the ComplexType paths no spec reached: unalias_and_qualify,
conforms_to_unique? with and without allow_any_match,
provides_duck_method?, all_rooted? and the deprecated
duck_types_match?, plus UniqueType#nullable? and the UniqueType copy
of intersect_with that BaseVariable#adjust_type reaches when #exclude
returns a bare UniqueType.
Clip#infer compares result.tags, the whole union, before taking the
Class#new BasicObject path. A Class#new returning 'Class, nil' used to
match on its first member and come back as BasicObject; this spec
pins the union staying intact.
The spec redefined Class#new in source, leaving two Class#new pins.
ApiMap#inner_get_methods orders them with an unstable sort by name,
so which pin answered depended on the platform's sort: the source pin
on macOS, the RBS pin (undefined) on CI's Linux runners.

Build the union from core's single Class#new instead: a safe-navigated
call on a Class<Class>, nil receiver infers 'Class, nil', which master
reported as BasicObject.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant