class Carbon::Concrete::Type
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.
Attributes
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<T, E>") type.generics # => [#<Carbon::Concrete::Type::Generic T>, # #<Carbon::Concrete::Type::Generic E>]
@return [<Type::Generic>]
The location of the type. This is used for interfacing with other programs that require a location of some sort.
@api semiprivate @return [Object]
The name of the type. This is from the parsed result. This contains the part and function information.
@api private @return [Type::Name]
Public Class Methods
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}]
# File lib/carbon/concrete/type.rb, line 28 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]
# File lib/carbon/concrete/type.rb, line 46 def self.from(string) return string if string.is_a?(Type) cache.compute_if_absent(string) { new(Parse.new(string).value) } end
Initialize the type with the given name. After initialization, this freezes the type, preventing modification.
@api public @param name [Type::Name] The name.
# File lib/carbon/concrete/type.rb, line 83 def initialize(name, location: nil) @name = name @generics = name.last.generics @generics += function.generics if function? @location = location deep_freeze! end
Public Instance Methods
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<T>" second.to_s # => "Carbon::Pointer<T>" 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.
# File lib/carbon/concrete/type.rb, line 147 def ==(other) equal?(other) || (other.is_a?(Type) && other.to_s == to_s) end
Accepts the current visitor unto itself.
@param visitor [#visit] @return [Object]
# File lib/carbon/concrete/type.rb, line 303 def accept(visitor, *params) visitor.visit(self, *params) 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 [<Type>] The parameters that the function is
called with.
@return [Type] The new type.
# File lib/carbon/concrete/type.rb, line 207 def call(name, parameters, generics = []) func = Type::Function.new(name.to_s, parameters, generics) name = Name.new(@name.parts, func) Type.new(name) 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<T>" type.function? # => false type.function # => nil
@example A function.
type.to_s # => "Carbon::Pointer<T>.add(Carbon::Pointer<T>, Carbon::Int32)" type.function? # => true type.function # => #<Carbon::Concrete::Type::Function ...>
@return [Concrete::Type::Function] The function information, if this is
a function type; otherwise,
@return [nil] returns nil.
# File lib/carbon/concrete/type.rb, line 124 def function @name.function 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<T>" type.function? # => false
@example A function.
type.to_s # => "Carbon::Pointer<T>.add(Carbon::Pointer<T>, Carbon::Int32)" type.function? # => true
@return [::Boolean] True if the type is a function, false otherwise.
# File lib/carbon/concrete/type.rb, line 104 def function? !function.nil? end
Creates a hash of the request. This is used mostly in the Hash class.
@api private @return [::Numeric] The hash.
# File lib/carbon/concrete/type.rb, line 176 def hash to_s.hash end
Pretty inspect.
@api private @return [::String] An inspection string.
# File lib/carbon/concrete/type.rb, line 295 def inspect "#<Carbon::Concrete::Type #{self}>".freeze 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.
# File lib/carbon/concrete/type.rb, line 191 def intern @name.intern end
Checks if the given type “matches” the current type. This matches if any of the following conditions apply:
-
If the types match exactly, with no generics; otherwise,
-
If the types match exactly, with generics; otherwise,
-
If the given type can be made to look like the item type by
introducing generics; otherwise,
-
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.
# File lib/carbon/concrete/type.rb, line 166 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
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 # => #<Carbon::Concrete::Type Carbon::Pointer<T>> mapping # => {"T" => #<Carbon::Concrete::Type Carbon::Int32>} subbed = type.sub(mapping) subbed # => #<Carbon::Concrete::Type Carbon::Pointer<Carbon::Int32>>
@example Special replacement.
type # => #<Carbon::Concrete::Type T> mapping # => {"T" => #<Carbon::Concrete::Type Carbon::Int32>} subbed = type.sub(mapping) subbed # => #<Carbon::Concrete::Type Carbon::Int32>
@param generics [{::String => Carbon::Concrete::Type}] The generics to
replace.
@return [Concrete::Type] The new type.
# File lib/carbon/concrete/type.rb, line 233 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
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]
# File lib/carbon/concrete/type.rb, line 271 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<Carbon::Boolean>"
@return [Type] The encapsulated type.
# File lib/carbon/concrete/type.rb, line 284 def to_pointer ptype = Carbon::Type("Carbon::Pointer<T>") tgen = Carbon::Type("T") ptype.sub(tgen => self) 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<T>") type.to_s # => "Carbon::Pointer<T>"
@example Re-parsing string.
form = "Carbon::Pointer<T: Carbon::Sized + Carbon::Numeric>" \ ".add(Carbon::Pointer<T: Carbon::Sized + Carbon::Numeric>, " \ "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.
# File lib/carbon/concrete/type.rb, line 257 def to_s @name.to_s end
Private Instance Methods
# File lib/carbon/concrete/type.rb, line 338 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<T>.+(A<T>, T)` would match `A<B>.+(A<B>, B)`, but not # `A<B>.+(A<B>, C)`. params.all? do |(ours, theirs)| if mapping.key?(ours) mapping[ours].match?(theirs, mapping) else ours.match?(theirs, mapping) end end && mapping end
# File lib/carbon/concrete/type.rb, line 325 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
# File lib/carbon/concrete/type.rb, line 318 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
# File lib/carbon/concrete/type.rb, line 357 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
# File lib/carbon/concrete/type.rb, line 309 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