# encoding: utf-8 # frozen_string_literal: true require "set" require "carbon/concrete/type/function" require "carbon/concrete/type/generic" require "carbon/concrete/type/name" require "carbon/concrete/type/part" require "carbon/concrete/type/parse" module Carbon module Concrete # A type. This is, basically, any possible name that could resolve # into an actual type. Modules or functions are included in the # typing. The Type class includes a parsing module, allowing types # to be stored as a string and then re-serialized as a Type. # # @note # **This class is frozen upon initialization.** This means that any # attempt to modify it will result in an error. In most cases, the # attributes on this class will also be frozen, as well. class Type # A cache of types. This maps unparsed strings to their parsed # derivatives. The cache is guarenteed to be thread-safe. # # @api private # @return [{::String => Type}] def self.cache @cache ||= Concurrent::Map.new end # Returns the corresponding type for the given string value. If # a type is not cached in {.cache}, then it computes it using # {Parse}. The cache is locked during the computation, such that # it won't be modified while the value is being parsed. # # @api public # @example # type = Carbon::Type("Carbon::Pointer") # type.to_s # => "Carbon::Pointer" # type.function? # => false # second = Carbon::Type("Carbon::Pointer") # type.equal?(second) # => true # @param string [::String] The type to parse. # @return [Type] def self.from(string) return string if string.is_a?(Type) cache.compute_if_absent(string) { new(Parse.new(string).value) } end # The name of the type. This is from the parsed result. This # contains the part and function information. # # @api private # @return [Type::Name] attr_reader :name # The generic information for the type. This is derived directly # from the last part in the name and the function name (if it # exists). # # @api public # @example # type = Carbon::Type("Result") # type.generics # # => [#, # # #] # @return [] attr_reader :generics # The location of the type. This is used for interfacing with other # programs that require a location of some sort. # # @api semiprivate # @return [Object] attr_reader :location # Initialize the type with the given name. After initialization, this # freezes the type, preventing modification. # # @api public # @param name [Type::Name] The name. def initialize(name, location: nil) @name = name @generics = name.last.generics @generics += function.generics if function? @location = location deep_freeze! end # Returns whether or not this type is a function type. It does this by # checking to see if the {Type::Name#function} attribute on the {#name} # is not nil. # # @api public # @example Not a function. # type.to_s # => "Carbon::Pointer" # type.function? # => false # @example A function. # type.to_s # # => "Carbon::Pointer.add(Carbon::Pointer, Carbon::Int32)" # type.function? # => true # @return [::Boolean] True if the type is a function, false otherwise. def function? !function.nil? end # Returns the function information of the type. If this isn't a function # type (see {#function?}), then this returns `nil`. # # @api public # @example Not a function. # type.to_s # => "Carbon::Pointer" # type.function? # => false # type.function # => nil # @example A function. # type.to_s # # => "Carbon::Pointer.add(Carbon::Pointer, Carbon::Int32)" # type.function? # => true # type.function # => # # @return [Concrete::Type::Function] The function information, if this is # a function type; otherwise, # @return [nil] returns nil. def function @name.function end # Compares this type to another type. If the other type _is_ this type, # it returns true; otherwise, if the other value is a {Type} and the # other type's {#to_s} is equal to this one's, it returns true; # otherwise, it returns false. # # @api public # @example Compare with self. # second = type # type.equal?(second) # => true # type == second # => true # @example Compare with a type. # type.equal?(second) # => false # type.to_s # => "Carbon::Pointer" # second.to_s # => "Carbon::Pointer" # type.to_s == second.to_s # => true # type == second # => true # @param other [Type, ::String, ::Object] The other object to compare. # @return [::Boolean] True if the other object is similar to this type; # false otherwise. def ==(other) equal?(other) || (other.is_a?(Type) && other.to_s == to_s) end alias_method :eql?, :== # Checks if the given type "matches" the current type. This # matches if any of the following conditions apply: # # 1. If the types match exactly, with no generics; otherwise, # 2. If the types match exactly, with generics; otherwise, # 3. If the given type can be made to look like the item type by # introducing generics; otherwise, # 4. They don't match. # # The base item provides a good default for most items. # # @param type [Concrete::Type] The type to check for matching. # @return [Boolean] If the type matches the item. def match?(other, generics = {}) (@generics.none? && to_s == other.to_s && generics) || (!function? && match_generic_module?(other, generics)) || (function? && match_generic_function?(other, generics)) end # Creates a hash of the request. This is used mostly in the Hash class. # # @api private # @return [::Numeric] The hash. def hash to_s.hash end # The interned name of a module. This is used for module lookup. This # contains no generic information. For generic functions, all generics # are pushed onto the defining module. # # @api public # @example # type.intern # => "Carbon::Pointer" # @note See {Item::Base#intern} For more information about interned # names. # @see Type::Name#intern # @return [::String] The interned name of the type. def intern @name.intern end # Creates a new type using this type as a base. The new type is a # function type; this provides information for the function type. # # @api semipublic # @example # bool = Carbon::Boolean # func = bool.call("<=>", [bool, bool]) # func.to_s # => "Carbon::Boolean.<=>(Carbon::Boolean, Carbon::Boolean)" # @param name [::String] The name of the function that is being defined. # @param parameters [] The parameters that the function is # called with. # @return [Type] The new type. def call(name, parameters, generics = []) func = Type::Function.new(name.to_s, parameters, generics) name = Name.new(@name.parts, func) Type.new(name) end # Replaces all of the generics in the {Type} with the mapped ones. This # is used when generics are fully resolved. # # @note # This also replaces the type itself if the type matches any of the # keys in the generic mapping. # @api semipublic # @example Normal replacement. # type # => #> # mapping # => {"T" => #} # subbed = type.sub(mapping) # subbed # => #> # @example Special replacement. # type # => # # mapping # => {"T" => #} # subbed = type.sub(mapping) # subbed # => # # @param generics [{::String => Carbon::Concrete::Type}] The generics to # replace. # @return [Concrete::Type] The new type. def sub(generics) return sub_function(generics) if function? return generics[self] if generics.key?(self) return self if generics.none? replace_generics(generics) end # Creates a string representation of the type for comparison or storage. # The string representation has a constraint that it must be re-parsable # by the {Type::Parse} class. # # @api public # @example String representation. # type = Carbon::Type("Carbon::Pointer") # type.to_s # => "Carbon::Pointer" # @example Re-parsing string. # form = "Carbon::Pointer" \ # ".add(Carbon::Pointer, " \ # "Carbon::Int32)" # type = Carbon::Type(form) # type.to_s == form # => true # second = Carbon::Type(type.to_s) # type == second # => true # @return [::String] The string representation of the type. def to_s @name.to_s end # Removes all function information from the type. This is the complete # opposite of {#call}. This just leaves the module that the function # was defined on. # # @api semipublic # @example # func = Carbon::Boolean.call("test") # func.to_s # => "Carbon::Boolean.test()" # func.to_module.to_s # => "Carbon::Boolean" # @return [Type] def to_module name = Name.new(@name.parts, nil) Type.new(name) end # Converts the type into a pointer type. Essentially, it just # encapsulates the current type in a pointer. # # @example # inner = Carbon::Boolean # inner.to_s # => "Carbon::Boolean" # inner.to_pointer.to_s # => "Carbon::Pointer" # @return [Type] The encapsulated type. def to_pointer ptype = Carbon::Type("Carbon::Pointer") tgen = Carbon::Type("T") ptype.sub(tgen => self) end # Pretty inspect. # # @api private # @return [::String] An inspection string. def inspect "#".freeze end # Accepts the current visitor unto itself. # # @param visitor [#visit] # @return [Object] def accept(visitor, *params) visitor.visit(self, *params) end private def sub_function(generics) return self if !@generics.any? && function.parameters.all? { |p| !p.generics.any? } basic = to_module.sub(generics) gens = function.generics.map { |p| p.sub(generics) } params = function.parameters.map { |p| p.sub(generics) } basic.call(function.name, params, gens) end def match_generic_module?(other, generics) return intern == other.intern && {} unless generics.any? intern == other.intern && other.generics.all? { |g| generics.values.include?(g.name) } && generics end def match_generic_function_preflight?(other, _) # If neither have generics, then this can't match them. @generics.any? && other.generics.any? && # If the types are modules, then they should have matched earlier. function? && other.function? && # The function names should be the same. (function.name == other.function.name) && # They should have the same number of generics. (@generics.size == other.generics.size) && # They should have the same number of parameters. (function.parameters.size == other.function.parameters.size) end def match_generic_function?(other, generics) return false unless match_generic_function_preflight?(other, generics) mapping = generics.merge(@generics.map(&:name) .zip(other.generics.map(&:name)).to_h) params = function.parameters.zip(other.function.parameters) # If any of the parameters don't match, then the whole doesn't match. # However, if one of the parameters is a generic, then we check to make # sure that the same generic placement is used in the other type. # For example, `A.+(A, T)` would match `A.+(A, B)`, but not # `A.+(A, C)`. params.all? do |(ours, theirs)| if mapping.key?(ours) mapping[ours].match?(theirs, mapping) else ours.match?(theirs, mapping) end end && mapping end def replace_generics(generics) part = Part.new(@name.parts.last.value, @generics.map { |g| g.sub(generics) }) name = Name.new([*@name.parts[0..-2], part], @name.function) Type.new(name) end end end end