Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Resolver::ConstantResolver and Resolver::TypeNameResolver #938

Merged
merged 3 commits into from
Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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