# encoding: utf-8 # frozen_string_literal: true require "forwardable" module Carbon module Concrete # A "request." This is used to note the generics that a given module # should be compiled with. This is used for items to note the dependencies # that an item has; otherwise, it is used in the index for build # information. # # @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 Request extend Forwardable # 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 # request.intern # => "Carbon::Pointer" # @note See {Item::Base#intern} For more information about interned # names. # @return [::String] attr_reader :intern # The generics of the request. Even if the request has no definable # generics, these are still kept in place for both name information and # for placement information (e.g. identity and size). # # @api public # @example # request.generics # => [#] # @return [] attr_reader :generics # Initialize the request with the given interned name and generics, # and then freezes the request. # # @api public # @see #intern # @see #generics # @param intern [::String] The interned name. # @param generics [] The generics. def initialize(intern, generics) @intern = intern @generics = generics deep_freeze! end # Replaces this request or this request's generics with the corresponding # correct module. This is designed to resolve generic names; for # example, it resolves `Carbon::Pointer` to # `Carbon::Pointer` with the right argument. It checks # for three seperate cases. # # 1. The request has no generics, and the request's {#intern} has a # mapping in the argument. In this case, it creates a new request # object with the new module's name and no generics (because the # mapping could not have added any worthwhile generics). # 2. The request has no generics, and the request's {#intern} has no # mapping in the argument. In this case, it returns itself. # 3. The request has generics. In this case, the generics are mapped # using {Type::Generic#sub}, and a new request is created with the # mapped generics. # # @api public # @example # string # => # # request.intern # => "Carbon::Pointer" # request.generics # => [#] # mapped = request.sub("T" => string) # mapped.intern # => "Carbon::Pointer" # request.generics.first.equal?(string) # => true # @param mapping [{::String => Type::Generic}] The mapping. # @return [Request] The new request, if the request has no generics, # and the request's {#intern} is not a key in the mapping, or if # the request has generics. # @return [self] If the request has no generics and it is not a key # in the mapping. def sub(mapping) case [@generics.none?, mapping.key?(@intern)] when [true, true] Request.new(mapping[@intern].name.intern, []) when [true, false] self else generics = @generics.map { |generic| generic.sub(mapping) } Request.new(intern, generics) end end # Compares this request to another. If this request _is_ the other # request, it returns true; otherwise, if the other is a request, and # the other request's {#intern} is equal to this request's, it # returns true; otherwise, it returns false. # # @api public # @param other [Request, ::String, ::Object] The object to compare. # @return [::Boolean] def ==(other) equal?(other) || (other.is_a?(Request) && other.intern == intern && other.generics == generics) end alias_method :eql?, :== # Creates a hash of the request. This is used mostly in the Hash # class. # # @api private # @return [Numeric] def hash @intern.hash end # Returns a string representation of this request. This includes all # generics that are associated with the request, if applicable. # # @api public # @return [::String] def to_s if @generics.any? "#{@intern}<#{@generics.map(&:to_s).join(', ')}>".freeze else @intern end end end end end