# typed: strict# frozen_string_literal: truemoduleRubyLsp# A minimalistic type checker to try to resolve types that can be inferred without requiring a type system or# annotationsclassTypeInferrer#: (RubyIndexer::Index index) -> voiddefinitialize(index)@index=indexend#: (NodeContext node_context) -> Type?definfer_receiver_type(node_context)node=node_context.nodecasenodewhenPrism::CallNodeinfer_receiver_for_call_node(node,node_context)whenPrism::InstanceVariableReadNode,Prism::InstanceVariableAndWriteNode,Prism::InstanceVariableWriteNode,Prism::InstanceVariableOperatorWriteNode,Prism::InstanceVariableOrWriteNode,Prism::InstanceVariableTargetNode,Prism::SuperNode,Prism::ForwardingSuperNodeself_receiver_handling(node_context)whenPrism::ClassVariableAndWriteNode,Prism::ClassVariableWriteNode,Prism::ClassVariableOperatorWriteNode,Prism::ClassVariableOrWriteNode,Prism::ClassVariableReadNode,Prism::ClassVariableTargetNodeinfer_receiver_for_class_variables(node_context)endendprivate#: (Prism::CallNode node, NodeContext node_context) -> Type?definfer_receiver_for_call_node(node,node_context)receiver=node.receiver# For receivers inside parenthesis, such as ranges like (0...2), we need to unwrap the parenthesis to get the# actual nodeifreceiver.is_a?(Prism::ParenthesesNode)statements=receiver.bodyifstatements.is_a?(Prism::StatementsNode)body=statements.bodyifbody.length==1receiver=body.firstendendendcasereceiverwhenPrism::SelfNode,nilself_receiver_handling(node_context)whenPrism::StringNodeType.new("String")whenPrism::SymbolNodeType.new("Symbol")whenPrism::ArrayNodeType.new("Array")whenPrism::HashNodeType.new("Hash")whenPrism::IntegerNodeType.new("Integer")whenPrism::FloatNodeType.new("Float")whenPrism::RegularExpressionNodeType.new("Regexp")whenPrism::NilNodeType.new("NilClass")whenPrism::TrueNodeType.new("TrueClass")whenPrism::FalseNodeType.new("FalseClass")whenPrism::RangeNodeType.new("Range")whenPrism::LambdaNodeType.new("Proc")whenPrism::ConstantPathNode,Prism::ConstantReadNode# When the receiver is a constant reference, we have to try to resolve it to figure out the right# receiver. But since the invocation is directly on the constant, that's the singleton context of that# class/modulereceiver_name=RubyIndexer::Index.constant_name(receiver)returnunlessreceiver_nameresolved_receiver=@index.resolve(receiver_name,node_context.nesting)name=resolved_receiver&.first&.namereturnunlessname*parts,last=name.split("::")returnType.new("#{last}::<Class:#{last}>")ifparts.empty?Type.new("#{parts.join("::")}::#{last}::<Class:#{last}>")whenPrism::CallNoderaw_receiver=receiver.messageifraw_receiver=="new"# When invoking `new`, we recursively infer the type of the receiver to get the class type its being invoked# on and then return the attached version of that type, since it's being instantiated.type=infer_receiver_for_call_node(receiver,node_context)returnunlesstype# If the method `new` was overridden, then we cannot assume that it will return a new instance of the classnew_method=@index.resolve_method("new",type.name)&.firstreturnifnew_method&&new_method.owner&.name!="Class"type.attachedelsifraw_receiverguess_type(raw_receiver,node_context.nesting)endelseguess_type(receiver.slice,node_context.nesting)endend#: (String raw_receiver, Array[String] nesting) -> GuessedType?defguess_type(raw_receiver,nesting)guessed_name=raw_receiver.delete_prefix("@").delete_prefix("@@").split("_").map(&:capitalize).joinentries=@index.resolve(guessed_name,nesting)||@index.first_unqualified_const(guessed_name)name=entries&.first&.namereturnunlessnameGuessedType.new(name)end#: (NodeContext node_context) -> Typedefself_receiver_handling(node_context)nesting=node_context.nesting# If we're at the top level, then the invocation is happening on `<main>`, which is a special singleton that# inherits from ObjectreturnType.new("Object")ifnesting.empty?returnType.new(node_context.fully_qualified_name)ifnode_context.surrounding_method# If we're not inside a method, then we're inside the body of a class or module, which is a singleton# context.## If the class/module definition is using compact style (e.g.: `class Foo::Bar`), then we need to split the name# into its individual parts to build the correct singleton nameparts=nesting.flat_map{|part|part.split("::")}Type.new("#{parts.join("::")}::<Class:#{parts.last}>")end#: (NodeContext node_context) -> Type?definfer_receiver_for_class_variables(node_context)nesting_parts=node_context.nesting.dupreturnType.new("Object")ifnesting_parts.empty?nesting_parts.reverse_eachdo|part|breakunlesspart.include?("<Class:")nesting_parts.popendreceiver_name=nesting_parts.join("::")resolved_receiver=@index.resolve(receiver_name,node_context.nesting)&.firstreturnunlessresolved_receiver&.nameType.new(resolved_receiver.name)end# A known typeclassType#: Stringattr_reader:name#: (String name) -> voiddefinitialize(name)@name=nameend# Returns the attached version of this type by removing the `<Class:...>` part from its name#: -> TypedefattachedType.new(@name.split("::")[..-2]#: as !nil.join("::"),)endend# A type that was guessed based on the receiver raw nameclassGuessedType<Typeendendend