# encoding: utf-8 # frozen_string_literal: true module Carbon module Concrete class Type # A generic part of a type. This is normally the `T` in # `Carbon::Pointer`. Generic portions can also have traits that # the generic type must implement; this keeps track of that too. # A complete example, containing traits, would look like this: # `Carbon::Pointer`. The actual # pointer type may not use those traits. # # @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 Generic # The name of the generic. This is the generic type variable that is # substituted in later for an actual value. This is normally a single # character module name, but can be as complicated as needed. # # @api public # @example # type = Carbon::Type("Carbon::Pointer") # type.generics.first.name # => # # @return [Type] The name. attr_reader :name # The implements of the generic. These are the traits that the generic # parameter has to implement; this allows the generic code to make # assumptions about the generic type (such as behavior or size) that # would otherwise be impossible to make. # # @api public # @example # name = "Carbon::Pointer" # type = Carbon::Type(name) # type.generics.first.implements # # => Set[#] # @return [Set] The traits the generic has to implement. attr_reader :implements # 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 generic part of the type. # # @see #name # @see #implements # @param name [Type] The name of the generic. # @param implements [Set] The traits the generic must # implement, if any. def initialize(name, implements, location: nil) @location = location @name = name @implements = Set.new(implements) deep_freeze! end # Returns the correct generic. This is a weird function, but # essentially, if the {#name}'s {Type#intern} is oen of the keys of # the mapping, it returns the new generic; otherwise, it returns # this object. # # @api public # @example # generic.to_s # => "T: Carbon::Sized" # generic.is_a?(Generic) # => true # other.to_s # => "Carbon::String" # other.is_a?(Generic) # => true # result = generic.sub("T" => other) # result.to_s # => "Carbon::String: Carbon::Sized" # @param mapping [{::String => Type}] The mapping. # @return [Type::Generic] A different instance of the generic, if it's # in the mapping. # @return [self] otherwise. def sub(mapping) Generic.new(mapping.fetch(@name, @name), @implements) end # Compares this generic instance to another generic instance. If the # other generic instance _is_ this generic instance it returns true; # otherwise, if the other value is a generic, and that generic's # {#to_s} is equal to this one's, it returns true; otherwise, it # returns false. # # @api public # @example # type1 = Carbon::Type("Carbon::Pointer") # type2 = Carbon::Type("Carbon::List") # type1.generics == type2.generics # => true # @param other [Type::Generic, ::Object] The object to compare. # @return [::Boolean] True if the two are equivalent. def ==(other) equal?(other) || (other.is_a?(Type::Generic) && other.to_s == to_s) end alias_method :eql?, :== # A string representation of the generic. If there are no implements, # this is a direct call to {#name}'s {Type#to_s}; otherwise, this # includes the name with a joined string of implements. # # @api public # @example # name = "Carbon::Pointer" # type = Carbon::Type(name) # type.generics.first.to_s # => "T: Carbon::Sized" # @return [::String] The string representation. def to_s if @implements.any? "#{@name}: #{@implements.map(&:to_s).join(' + ')}".freeze else @name.to_s end end # Accepts the current visitor unto itself. # # @param visitor [#visit] # @return [Object] def accept(visitor, *params) visitor.visit(self, *params) end end end end end