diff --git a/lib/repl_type_completor/type_analyzer.rb b/lib/repl_type_completor/type_analyzer.rb index c14357d..9a15bc6 100644 --- a/lib/repl_type_completor/type_analyzer.rb +++ b/lib/repl_type_completor/type_analyzer.rb @@ -177,15 +177,15 @@ def evaluate_hash(node, scope) hash = method_call hash, :to_hash, [], nil, nil, scope end if hash.is_a?(Types::InstanceType) && hash.klass == Hash - keys << hash.params[:K] if hash.params[:K] - values << hash.params[:V] if hash.params[:V] + keys << hash.params[Types.hash_key_type_param] if hash.params[Types.hash_key_type_param] + values << hash.params[Types.hash_value_type_param] if hash.params[Types.hash_value_type_param] end end end if keys.empty? && values.empty? Types::InstanceType.new Hash else - Types::InstanceType.new Hash, K: Types::UnionType[*keys], V: Types::UnionType[*values] + Types::InstanceType.hash_with_params(Types::UnionType[*keys], Types::UnionType[*values]) end end @@ -706,7 +706,7 @@ def evaluate_for_node(node, scope) inner_scope = Scope.new scope, { Scope::BREAK_RESULT => nil } ary_type = method_call collection, :to_ary, [], nil, nil, nil, name_match: false element_types = ary_type.types.filter_map do |ary| - ary.params[:Elem] if ary.is_a?(Types::InstanceType) && ary.klass == Array + ary.params[Types.array_elem_type_param] if ary.is_a?(Types::InstanceType) && ary.klass == Array end element_type = Types::UnionType[*element_types] inner_scope.conditional do |s| @@ -761,7 +761,7 @@ def evaluate_range_node(node, scope) beg_type = evaluate node.left, scope if node.left end_type = evaluate node.right, scope if node.right elem = (Types::UnionType[*[beg_type, end_type].compact]).nonnillable - Types::InstanceType.new Range, Elem: elem + Types::InstanceType.new Range, Types.array_elem_type_param => elem end def evaluate_defined_node(node, scope) @@ -958,7 +958,7 @@ def assign_parameters(node, scope, args, kwargs) end # node.keyword_rest is Prism::KeywordRestParameterNode or Prism::ForwardingParameterNode or Prism::NoKeywordsParameterNode if node.keyword_rest.is_a?(Prism::KeywordRestParameterNode) && node.keyword_rest.name - scope[node.keyword_rest.name.to_s] = Types::InstanceType.new(Hash, K: Types::SYMBOL, V: Types::UnionType[*kwargs.values]) + scope[node.keyword_rest.name.to_s] = Types::InstanceType.hash_with_params(Types::SYMBOL, Types::UnionType[*kwargs.values]) end if node.block&.name # node.block is Prism::BlockParameterNode @@ -1143,7 +1143,7 @@ def partition_to_array(value, method) true end end - array_elem = arrays.empty? ? nil : Types::UnionType[*arrays.map { _1.params[:Elem] || Types::OBJECT }] + array_elem = arrays.empty? ? nil : Types::UnionType[*arrays.map { _1.params[Types.array_elem_type_param] || Types::OBJECT }] non_array = non_arrays.empty? ? nil : Types::UnionType[*non_arrays] [array_elem, non_array] end diff --git a/lib/repl_type_completor/types.rb b/lib/repl_type_completor/types.rb index 7a05ea3..81a537e 100644 --- a/lib/repl_type_completor/types.rb +++ b/lib/repl_type_completor/types.rb @@ -52,6 +52,25 @@ def self.load_rbs_builder nil end + def self.array_elem_type_param + (@array_elem_type_param ||= _class_type_params(Array)&.first) || :E + end + + def self.hash_type_params + (@hash_type_params ||= _class_type_params(Hash)) || [:K, :V] + end + + def self.hash_key_type_param = hash_type_params&.first + + def self.hash_value_type_param = hash_type_params&.last + + def self._class_type_params(klass) + return unless rbs_builder + + type_name = rbs_absolute_type_name(class_name_of(klass)) + rbs_builder.build_instance(type_name).type_params + end + def self.class_name_of(klass) while true name = Methods::MODULE_NAME_METHOD.bind_call klass @@ -156,8 +175,7 @@ def self.rbs_methods(type, method_name, args_types, kwargs_type, has_block) keyrest = method_type.type.rest_keywords args = args_types if kwargs_type&.any? && keyreqs.empty? && keyopts.empty? && keyrest.nil? - kw_value_type = UnionType[*kwargs_type.values] - args += [InstanceType.new(Hash, K: SYMBOL, V: kw_value_type)] + args += [InstanceType.hash_with_params(Types::SYMBOL, UnionType[*kwargs_type.values])] end if has_splat score += 1 if args.count(&:itself) <= reqs.size + opts.size + trailings.size @@ -276,13 +294,13 @@ def expand_params if @klass == Array type = Types.union_type_from_objects_list(@instances) - { Elem: UnionType[*params[:Elem], *type] } + { Types.array_elem_type_param => UnionType[*params[Types.array_elem_type_param], *type] } elsif @klass == Hash key = Types.union_type_from_objects_list(@instances.map(&:keys)) value = Types.union_type_from_objects_list(@instances.map(&:values)) { - K: UnionType[*params[:K], key], - V: UnionType[*params[:V], value] + Types.hash_key_type_param => UnionType[*params[Types.hash_key_type_param], key], + Types.hash_value_type_param => UnionType[*params[Types.hash_value_type_param], value] } else params @@ -324,6 +342,14 @@ def inspect_without_params klass.to_s end end + + def self.array_with_params(elem_type) + new(Array, { Types.array_elem_type_param => elem_type }) + end + + def self.hash_with_params(key_type, value_type) + new(Hash, { Types.hash_key_type_param => key_type, Types.hash_value_type_param => value_type }) + end end NIL = InstanceType.new NilClass @@ -405,7 +431,7 @@ def inspect() = @types.map(&:inspect).sort.join(' | ') def self.array_of(*types) type = types.size >= 2 ? UnionType[*types] : types.first || OBJECT - InstanceType.new Array, Elem: type + InstanceType.array_with_params(type) end def self.from_rbs_type(return_type, self_type, extra_vars = {}) @@ -445,9 +471,9 @@ def self.from_rbs_type(return_type, self_type, extra_vars = {}) PROC when RBS::Types::Tuple elem = UnionType[*return_type.types.map { from_rbs_type _1, self_type, extra_vars }] - InstanceType.new Array, Elem: elem + InstanceType.array_with_params(elem) when RBS::Types::Record - InstanceType.new Hash, K: SYMBOL, V: OBJECT + InstanceType.hash_with_params(Types::SYMBOL, Types::OBJECT) when RBS::Types::Literal InstanceType.new return_type.literal.class when RBS::Types::Variable @@ -516,7 +542,7 @@ def self._match_free_variable(vars, rbs_type, value, accumulator) _match_free_variable vars, arg, v, accumulator if v end in [RBS::Types::Tuple, InstanceType] if value.klass == Array - v = value.params[:Elem] + v = value.params[array_elem_type_param] rbs_type.types.each do |t| _match_free_variable vars, t, v, accumulator end diff --git a/test/repl_type_completor/test_types.rb b/test/repl_type_completor/test_types.rb index e665db8..30b9dba 100644 --- a/test/repl_type_completor/test_types.rb +++ b/test/repl_type_completor/test_types.rb @@ -11,14 +11,14 @@ def test_type_inspect nil_type = ReplTypeCompletor::Types::NIL string_type = ReplTypeCompletor::Types::STRING true_or_false = ReplTypeCompletor::Types::UnionType[true_type, false_type] - array_type = ReplTypeCompletor::Types::InstanceType.new Array, { Elem: true_or_false } + array_type = ReplTypeCompletor::Types::InstanceType.array_with_params(true_or_false) assert_equal 'nil', nil_type.inspect assert_equal 'true', true_type.inspect assert_equal 'false', false_type.inspect assert_equal 'String', string_type.inspect assert_equal 'Array', ReplTypeCompletor::Types::InstanceType.new(Array).inspect assert_equal 'false | true', true_or_false.inspect - assert_equal 'Array[Elem: false | true]', array_type.inspect + assert_equal "Array[#{ReplTypeCompletor::Types.array_elem_type_param}: false | true]", array_type.inspect assert_equal 'Array', array_type.inspect_without_params assert_equal 'Proc', ReplTypeCompletor::Types::PROC.inspect assert_equal 'Array.itself', ReplTypeCompletor::Types::SingletonType.new(Array).inspect @@ -51,14 +51,14 @@ def bo.hash; 42; end # Needed to use this object as a hash key assert_equal Hash, hash_type.klass assert_equal Hash, bo_key_hash_type.klass assert_equal Hash, bo_value_hash_type.klass - assert_equal BasicObject, bo_arr_type.params[:Elem].klass - assert_equal BasicObject, bo_key_hash_type.params[:K].klass - assert_equal BasicObject, bo_value_hash_type.params[:V].klass + assert_equal BasicObject, bo_arr_type.params[ReplTypeCompletor::Types.array_elem_type_param].klass + assert_equal BasicObject, bo_key_hash_type.params[ReplTypeCompletor::Types.hash_key_type_param].klass + assert_equal BasicObject, bo_value_hash_type.params[ReplTypeCompletor::Types.hash_value_type_param].klass assert_equal 'Object', obj_type.inspect assert_equal 'Array[unresolved]', arr_type.inspect - assert_equal 'Array[Elem: Integer | String]', arr_type.tap(&:params).inspect + assert_equal "Array[#{ReplTypeCompletor::Types.array_elem_type_param}: Integer | String]", arr_type.tap(&:params).inspect assert_equal 'Hash[unresolved]', hash_type.inspect - assert_equal 'Hash[K: String, V: Symbol]', hash_type.tap(&:params).inspect + assert_equal "Hash[#{ReplTypeCompletor::Types.hash_key_type_param}: String, #{ReplTypeCompletor::Types.hash_value_type_param}: Symbol]", hash_type.tap(&:params).inspect assert_equal 'Array.itself', ReplTypeCompletor::Types.type_from_object(Array).inspect assert_equal 'ReplTypeCompletor.itself', ReplTypeCompletor::Types.type_from_object(ReplTypeCompletor).inspect end @@ -109,14 +109,14 @@ def test_params_lazily_expanded_on_recursive_type type = ReplTypeCompletor::Types.type_from_object a assert_equal Array, type.klass 10.times do |i| - elem_type = type.params[:Elem] + elem_type = type.params[ReplTypeCompletor::Types.array_elem_type_param] expected = i.even? ? [Array, String] : [Array, Symbol] assert_equal expected, elem_type.types.map(&:klass).sort_by(&:name) type = elem_type.types.find { _1.klass == Array } end - hash_type = type.params[:Elem].types.find { _1.klass == Hash } + hash_type = type.params[ReplTypeCompletor::Types.array_elem_type_param].types.find { _1.klass == Hash } assert_equal 'Hash[unresolved]', hash_type.inspect - assert_equal 'Hash[K: Integer, V: Float]', hash_type.tap(&:params).inspect + assert_equal "Hash[#{ReplTypeCompletor::Types.hash_key_type_param}: Integer, #{ReplTypeCompletor::Types.hash_value_type_param}: Float]", hash_type.tap(&:params).inspect end def test_infinite_nested_type_inspect @@ -124,7 +124,30 @@ def test_infinite_nested_type_inspect a << a type = ReplTypeCompletor::Types.type_from_object a assert_equal 'Array[unresolved]', type.inspect - assert_equal 'Array[Elem: Array[unresolved]]', type.tap(&:params).inspect + assert_equal "Array[#{ReplTypeCompletor::Types.array_elem_type_param}: Array[unresolved]]", type.tap(&:params).inspect + end + + def test_array_hash_type_params_fallback + rbs_builder = ReplTypeCompletor::Types.instance_variable_get(:@rbs_builder) + assert_include [:E, :Elem], ReplTypeCompletor::Types.array_elem_type_param + assert_equal :K, ReplTypeCompletor::Types.hash_key_type_param + assert_equal :V, ReplTypeCompletor::Types.hash_value_type_param + + ReplTypeCompletor::Types.instance_variable_set(:@rbs_builder, nil) + type_param_cach_ivars = %i[@array_elem_type_param @hash_type_params] + type_param_cach_ivars.each do |ivar| + assert ReplTypeCompletor::Types.instance_variable_get(ivar) + ReplTypeCompletor::Types.instance_variable_set(ivar, nil) + end + + assert_equal :E, ReplTypeCompletor::Types.array_elem_type_param + assert_equal :K, ReplTypeCompletor::Types.hash_key_type_param + assert_equal :V, ReplTypeCompletor::Types.hash_value_type_param + type_param_cach_ivars.each do |ivar| + refute ReplTypeCompletor::Types.instance_variable_get(ivar) + end + ensure + ReplTypeCompletor::Types.instance_variable_set(:@rbs_builder, rbs_builder) if rbs_builder end end end