Skip to content

Commit

Permalink
Merge pull request #938 from ruby/const-resolver
Browse files Browse the repository at this point in the history
Add Resolver::ConstantResolver and Resolver::TypeNameResolver
  • Loading branch information
soutaro authored Mar 31, 2022
2 parents 2d4c8f7 + 0f06f86 commit ba0334b
Show file tree
Hide file tree
Showing 11 changed files with 828 additions and 4 deletions.
2 changes: 2 additions & 0 deletions lib/rbs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
require "rbs/variance_calculator"
require "rbs/substitution"
require "rbs/constant"
require "rbs/resolver/constant_resolver"
require "rbs/resolver/type_name_resolver"
require "rbs/constant_table"
require "rbs/ast/comment"
require "rbs/writer"
Expand Down
192 changes: 192 additions & 0 deletions lib/rbs/resolver/constant_resolver.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
module RBS
module Resolver
class ConstantResolver
class Table
attr_reader :children_table, :toplevel
attr_reader :constants_table

def initialize(environment)
@children_table = {}
@toplevel = {}

@constants_table = {}

environment.class_decls.each_key do |name|
children_table[name] = {}
end

environment.class_decls.each do |name, entry|
unless name.namespace.empty?
parent = name.namespace.to_type_name

table = children_table[parent] or raise
constant = constant_of_module(name, entry)
else
table = toplevel
constant = constant_of_module(name, entry)
end

table[name.name] = constant
constants_table[name] = constant
end

environment.constant_decls.each do |name, entry|
unless name.namespace.empty?
parent = name.namespace.to_type_name

table = children_table[parent] or raise
constant = constant_of_constant(name, entry)
else
table = toplevel
constant = constant_of_constant(name, entry)
end

table[name.name] = constant
end
end

def children(name)
children_table[name]
end

def constant(name)
constants_table[name]
end

def constant_of_module(name, entry)
type = Types::ClassSingleton.new(
name: name,
location: nil
)

Constant.new(name: name, type: type, entry: entry)
end

def constant_of_constant(name, entry)
Constant.new(name: name, type: entry.decl.type, entry: entry)
end
end

attr_reader :builder, :table
attr_reader :context_constants_cache, :child_constants_cache

def initialize(builder:)
@builder = builder
@table = Table.new(builder.env)
@context_constants_cache = {}
@child_constants_cache = {}
end

def resolve(name, context:)
cs = constants(context) or raise "Broken context is given"
cs[name]
end

def constants(context)
unless context_constants_cache.key?(context)
load_context_constants(context)
end

context_constants_cache[context]
end

def resolve_child(module_name, name)
children(module_name)[name]
end

def children(module_name)
unless child_constants_cache.key?(module_name)
load_child_constants(module_name)
end

child_constants_cache[module_name] or raise
end

def load_context_constants(context)
# @type var consts: Hash[Symbol, Constant]
consts = {}

if context
if last = context[1]
constants_from_ancestors(last, constants: consts)
end
end
constants_from_context(context, constants: consts) or return
constants_itself(context, constants: consts)

context_constants_cache[context] = consts
end

def load_child_constants(name)
# @type var constants: Hash[Symbol, Constant]
constants = {}

if table.children(name)
builder.ancestor_builder.instance_ancestors(name).ancestors.each do |ancestor|
if ancestor.is_a?(Definition::Ancestor::Instance)
if ancestor.name == BuiltinNames::Object.name
if name != BuiltinNames::Object.name
next
end
end

case ancestor.source
when AST::Members::Include, :super, nil
consts = table.children(ancestor.name) or raise
constants.merge!(consts)
end
end
end
end

child_constants_cache[name] = constants
end

def constants_from_context(context, constants:)
if context
parent, last = context

constants_from_context(parent, constants: constants) or return false

if last
consts = table.children(last) or return false
constants.merge!(consts)
end
else
constants.merge!(table.toplevel)
end

true
end

def constants_from_ancestors(module_name, constants:)
builder.ancestor_builder.instance_ancestors(module_name).ancestors.each do |ancestor|
if ancestor.is_a?(Definition::Ancestor::Instance)
case ancestor.source
when AST::Members::Include, :super, nil
consts = table.children(ancestor.name) or raise
constants.merge!(consts)
end
end
end
end

def constants_itself(context, constants:)
if context
_, typename = context

if typename
if (ns = typename.namespace).empty?
constant = table.toplevel[typename.name] or raise
else
hash = table.children(ns.to_type_name) or raise
constant = hash[typename.name]
end

constants[typename.name] = constant
end
end
end
end
end
end
55 changes: 55 additions & 0 deletions lib/rbs/resolver/type_name_resolver.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
module RBS
module Resolver
class TypeNameResolver
attr_reader :all_names
attr_reader :cache

def initialize(env)
@all_names = Set[]
@cache = {}

all_names.merge(env.class_decls.keys)
all_names.merge(env.interface_decls.keys)
all_names.merge(env.alias_decls.keys)
end

def try_cache(query)
cache.fetch(query) do
result = yield
cache[query] = result
end
end

def resolve(type_name, context:)
if type_name.absolute?
return type_name
end

try_cache([type_name, context]) do
resolve_in(type_name, context)
end
end

def resolve_in(type_name, context)
if context
parent, child = context
case child
when false
resolve_in(type_name, parent)
when TypeName
name = type_name.with_prefix(child.to_namespace)
has_name?(name) || resolve_in(type_name, parent)
end
else
has_name?(type_name.absolute!)
end
end

def has_name?(full_name)
if all_names.include?(full_name)
full_name
end
end
end
end
end
15 changes: 15 additions & 0 deletions lib/rbs/type_name.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,21 @@ def interface?
def with_prefix(namespace)
self.class.new(namespace: namespace + self.namespace, name: name)
end

def split
namespace.path + [name]
end

def +(other)
if other.absolute?
other
else
TypeName.new(
namespace: self.to_namespace + other.namespace,
name: other.name
)
end
end
end
end

Expand Down
93 changes: 93 additions & 0 deletions sig/resolver/constant_resolver.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
module RBS
module Resolver
class ConstantResolver
# Table stores the set of immediate child constants of a module.
#
# ```rb
# table = RBS::ConstantResolver::Table.new(env)
#
# table.children(TypeName("::Object")) # -> { ... } Returns a hash of name and constants.
# table.children(TypeName("::File::PATH_SEPARATOR")) # -> nil Returns nil because the constant is not a module.
#
# table.toplevel # -> { ... } Returns a hash of top level constants.
# ```
#
# The `#toplevel` is incompatible with Ruby.
# All constants in Ruby are defined under `Object`, and they are accessed with `::` (Colon3) operator.
# RBS is different.
# `::` constants are _toplevel_ constants, and they are not defined under `::Object`.
#
# The behavior is simulated in `ConstantResolver`.
#
class Table
attr_reader children_table: Hash[TypeName, Hash[Symbol, Constant]?]
attr_reader constants_table: Hash[TypeName, Constant]
attr_reader toplevel: Hash[Symbol, Constant]

def initialize: (Environment) -> void

def constant: (TypeName constant_name) -> Constant?

# Returns a set of constants defined under `module_name`.
# Returns `nil` if there is no module with given `module_name`.
#
def children: (TypeName module_name) -> Hash[Symbol, Constant]?

private

def constant_of_module: (TypeName name, Environment::ClassEntry | Environment::ModuleEntry) -> Constant

def constant_of_constant: (TypeName name, Environment::SingleEntry[TypeName, AST::Declarations::Constant]) -> Constant
end

attr_reader builder: DefinitionBuilder
attr_reader table: Table
attr_reader context_constants_cache: Hash[context, Hash[Symbol, Constant]?]
attr_reader child_constants_cache: Hash[TypeName, Hash[Symbol, Constant]]

def initialize: (builder: DefinitionBuilder) -> void

# Resolves to `Constant` with given constant name `name` and `context`.
# Returns `nil` if the constant cannot be resolved from the context.
#
def resolve: (Symbol name, context: context) -> Constant?

# Returns the available all constants from `context`.
#
# Returns `nil` when the `context` is invalid.
def constants: (context) -> Hash[Symbol, Constant]?

# Resolves the module_name and constant name to `Constant`
#
# ```ruby
# A::B
# ^ <- module_name
# ^ <- constant_name
# ```
#
# Find the
def resolve_child: (TypeName module_name, Symbol constant_name) -> Constant?

# Returns the table of all constants accessible with `::` (colon2) operator.
#
# * The constants under the module are included.
# * The constants under the ancestor modules are included.
# * The constants under the `::Object` class are not included.
# * The top level constants are not included.
#
def children: (TypeName module_name) -> Hash[Symbol, Constant]

private

def load_context_constants: (context) -> void

def load_child_constants: (TypeName) -> void

def constants_from_context: (context, constants: Hash[Symbol, Constant]) -> bool

def constants_from_ancestors: (TypeName, constants: Hash[Symbol, Constant]) -> void

def constants_itself: (context, constants: Hash[Symbol, Constant]) -> void
end
end
end
Loading

0 comments on commit ba0334b

Please sign in to comment.