Skip to content

Commit

Permalink
Merge pull request #1393 from ruby/normalize-type-names
Browse files Browse the repository at this point in the history
Fix module alias normalizations

# Conflicts:
#	sig/errors.rbs
  • Loading branch information
soutaro committed Jul 27, 2023
1 parent 8bce365 commit 62f61ac
Show file tree
Hide file tree
Showing 22 changed files with 553 additions and 103 deletions.
7 changes: 4 additions & 3 deletions lib/rbs/definition_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -753,12 +753,14 @@ def expand_alias(type_name)
end

def expand_alias1(type_name)
type_name = env.normalize_type_name(type_name)
entry = env.type_alias_decls[type_name] or raise "Unknown alias name: #{type_name}"
as = entry.decl.type_params.each.map { Types::Bases::Any.new(location: nil) }
expand_alias2(type_name, as)
end

def expand_alias2(type_name, args)
type_name = env.normalize_type_name(type_name)
entry = env.type_alias_decls[type_name] or raise "Unknown alias name: #{type_name}"

ensure_namespace!(type_name.namespace, location: entry.decl.location)
Expand Down Expand Up @@ -811,9 +813,8 @@ def validate_type_presence(type)
end

def validate_type_name(name, location)
name = name.absolute!

return if env.type_name?(name)
name = name.absolute! unless name.absolute?
return if env.type_name?(env.normalize_type_name(name))

raise NoTypeFoundError.new(type_name: name, location: location)
end
Expand Down
103 changes: 73 additions & 30 deletions lib/rbs/environment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -275,53 +275,96 @@ def constant_entry(type_name)
class_entry(type_name) || module_entry(type_name) || constant_decls[type_name]
end

def normalize_type_name?(name)
if name.class?
normalize_module_name?(name)
else
unless name.namespace.empty?
parent = name.namespace.to_type_name
parent = normalize_module_name?(parent)
return parent unless parent

TypeName.new(namespace: parent.to_namespace, name: name.name)
else
name
end
end
end

def normalize_type_name!(name)
result = normalize_type_name?(name)

case result
when TypeName
result
when false
raise "Type name `#{name}` cannot be normalized because it's a cyclic definition"
when nil
raise "Type name `#{name}` cannot be normalized because of unknown type name in the path"
end
end

def normalized_type_name?(type_name)
case
when type_name.interface?
interface_decls.key?(type_name)
when type_name.class?
class_decls.key?(type_name)
when type_name.alias?
type_alias_decls.key?(type_name)
else
false
end
end

def normalized_type_name!(name)
normalized_type_name?(name) or raise "Normalized type name is expected but given `#{name}`, which is normalized to `#{normalize_type_name?(name)}`"
name
end

def normalize_type_name(name)
normalize_type_name?(name) || name
end

def normalize_module_name(name)
normalize_module_name?(name) or name
end

def normalize_module_name?(name)
raise "Class/module name is expected: #{name}" unless name.class?
name = name.absolute! if name.relative!
name = name.absolute! unless name.absolute?

if @normalize_module_name_cache.key?(name)
return @normalize_module_name_cache[name]
end

unless name.namespace.empty?
parent = name.namespace.to_type_name
if normalized_parent = normalize_module_name?(parent)
type_name = TypeName.new(namespace: normalized_parent.to_namespace, name: name.name)
else
@normalize_module_name_cache[name] = nil
return
end
else
type_name = name
end

@normalize_module_name_cache[name] = false

entry = constant_entry(name)
case entry
when ClassEntry, ModuleEntry
@normalize_module_name_cache[name] = entry.name
entry.name
entry = constant_entry(type_name)

when ClassAliasEntry, ModuleAliasEntry
old_name = entry.decl.old_name
if old_name.namespace.empty?
@normalize_module_name_cache[name] = normalize_module_name?(old_name)
normalized_type_name =
case entry
when ClassEntry, ModuleEntry
type_name
when ClassAliasEntry, ModuleAliasEntry
normalize_module_name?(entry.decl.old_name)
else
parent = old_name.namespace.to_type_name

if normalized_parent = normalize_module_name?(parent)
@normalize_module_name_cache[name] =
if normalized_parent == parent
normalize_module_name?(old_name)
else
normalize_module_name?(
TypeName.new(name: old_name.name, namespace: normalized_parent.to_namespace)
)
end
else
@normalize_module_name_cache[name] = nil
end
nil
end

when ConstantEntry
raise "#{name} is a constant name"

else
@normalize_module_name_cache[name] = nil
end
@normalize_module_name_cache[name] = normalized_type_name
end

def insert_decl(decl, outer:, namespace:)
Expand Down
8 changes: 4 additions & 4 deletions lib/rbs/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ def location
end

def self.check!(super_decl, env:)
return if env.class_decl?(super_decl.name) || env.class_alias?(super_decl.name)
super_name = env.normalize_type_name(super_decl.name)
return if env.class_decl?(super_name) || env.class_alias?(super_name)

raise new(super_decl)
end
Expand All @@ -205,9 +206,8 @@ def initialize(type_name:, location:)
end

def self.check!(self_type, env:)
type_name = self_type.name

(env.module_name?(type_name) || env.interface_name?(type_name)) or raise new(type_name: type_name, location: self_type.location)
self_name = env.normalize_type_name(self_type.name)
(env.module_name?(self_name) || env.interface_name?(self_name)) or raise new(type_name: self_type.name, location: self_type.location)
end
end

Expand Down
7 changes: 6 additions & 1 deletion lib/rbs/resolver/type_name_resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ module Resolver
class TypeNameResolver
attr_reader :all_names
attr_reader :cache
attr_reader :env

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

all_names.merge(env.class_decls.keys)
all_names.merge(env.interface_decls.keys)
Expand All @@ -35,7 +37,10 @@ def resolve(type_name, context:)

if head
if tail
has_name?(tail.with_prefix(head.to_namespace))
absolute_name = tail.with_prefix(head.to_namespace)
if env.normalize_type_name?(absolute_name)
absolute_name
end
else
head
end
Expand Down
14 changes: 12 additions & 2 deletions lib/rbs/type_alias_dependency.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def circular_definition?(alias_name)
# Construct transitive closure, if not constructed already
transitive_closure() unless @dependencies

# Check for recursive type alias
alias_name = env.normalize_type_name!(alias_name)
@dependencies[alias_name][alias_name]
end

Expand Down Expand Up @@ -49,6 +49,16 @@ def transitive_closure
end
end

def direct_dependencies_of(name)
name = env.normalize_type_name!(name)
@direct_dependencies[name]
end

def dependencies_of(name)
name = env.normalize_type_name!(name)
@dependencies[name].each_key.to_set
end

private

# Constructs directed graph recursively
Expand All @@ -61,7 +71,7 @@ def direct_dependency(type, result = Set[])
end
when RBS::Types::Alias
# Append type name if the type is an alias
result << type.name
result << env.normalize_type_name(type.name)
end

result
Expand Down
29 changes: 18 additions & 11 deletions lib/rbs/type_alias_regularity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ def validate
end

def validate_alias_type(alias_type, names, types)
if names.include?(alias_type.name)
if ex_type = types[alias_type.name]
alias_name = env.normalize_type_name?(alias_type.name) or return

if names.include?(alias_name)
if ex_type = types[alias_name]
unless compatible_args?(ex_type.args, alias_type.args)
diagnostics[alias_type.name] ||=
diagnostics[alias_name] ||=
Diagnostic.new(type_name: alias_type.name, nonregular_type: alias_type)
end

Expand All @@ -49,7 +51,7 @@ def validate_alias_type(alias_type, names, types)
types[alias_type.name] = alias_type
end

expanded = builder.expand_alias2(alias_type.name, alias_type.args)
expanded = builder.expand_alias2(alias_name, alias_type.args)
each_alias_type(expanded) do |at|
validate_alias_type(at, names, types)
end
Expand All @@ -75,22 +77,27 @@ def compatible_args?(args1, args2)
end

def nonregular?(type_name)
diagnostics[type_name]
diagnostics[env.normalize_type_name!(type_name)]
end

def each_mutual_alias_defs(&block)
# @type var each_node: TSort::_EachNode[TypeName]
each_node = __skip__ = -> (&block) do
# @type var each_node: ^() { (TypeName) -> void } -> void
each_node = -> (&block) do
env.type_alias_decls.each_value do |decl|
block[decl.name]
if normalized = env.normalize_type_name?(decl.name)
block[normalized]
end
end
end
# @type var each_child: TSort::_EachChild[TypeName]
each_child = __skip__ = -> (name, &block) do

# @type var each_child: ^(TypeName) { (TypeName) -> void } -> void
each_child = -> (name, &block) do
if env.type_alias_decls.key?(name)
type = builder.expand_alias1(name)
each_alias_type(type) do |ty|
block[ty.name]
if normalized = env.normalize_type_name?(ty.name)
block[normalized]
end
end
end
end
Expand Down
57 changes: 29 additions & 28 deletions lib/rbs/validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,43 +12,44 @@ def initialize(env:, resolver:)
@definition_builder = DefinitionBuilder.new(env: env)
end

def absolute_type(type, context:)
def absolute_type(type, context:, &block)
type.map_type_name do |type_name, _, type|
resolver.resolve(type_name, context: context) || yield(type)
resolver.resolve(type_name, context: context) || (block ? yield(type) : type_name)
end
end

# Validates presence of the relative type, and application arity match.
def validate_type(type, context:)
case type
when Types::ClassInstance, Types::Interface, Types::Alias
# @type var type: Types::ClassInstance | Types::Interface | Types::Alias
if type.name.namespace.relative?
type = _ = absolute_type(type, context: context) do |_|
NoTypeFoundError.check!(type.name.absolute!, env: env, location: type.location)
end
end
type = absolute_type(type, context: context) #: Types::ClassInstance | Types::Interface | Types::Alias

definition_builder.validate_type_name(type.name, type.location)

type_params = case type
when Types::ClassInstance
entry = env.normalized_module_class_entry(type.name) or raise
entry.type_params
when Types::Interface
env.interface_decls[type.name].decl.type_params
when Types::Alias
env.type_alias_decls[type.name].decl.type_params
end

InvalidTypeApplicationError.check!(
type_name: type.name,
args: type.args,
params: type_params.each.map(&:name),
location: type.location
)
normalized_type_name = env.normalize_type_name?(type.name)

if normalized_type_name
type_params =
case type
when Types::ClassInstance
entry = env.class_decls[normalized_type_name] or raise
entry.type_params
when Types::Interface
env.interface_decls[normalized_type_name].decl.type_params
when Types::Alias
env.type_alias_decls[normalized_type_name].decl.type_params
end

InvalidTypeApplicationError.check!(
type_name: type.name,
args: type.args,
params: type_params.each.map(&:name),
location: type.location
)
end

when Types::ClassSingleton
type = absolute_type(type, context: context) #: Types::ClassSingleton
definition_builder.validate_type_presence(type)
end

Expand Down Expand Up @@ -115,14 +116,14 @@ def validate_method_definition(method_def, type_name:)
end

def validate_type_params(params, type_name: , method_name: nil, location:)
# @type var each_node: TSort::_EachNode[Symbol]
each_node = __skip__ = -> (&block) do
# @type var each_node: ^() { (Symbol) -> void } -> void
each_node = -> (&block) do
params.each do |param|
block[param.name]
end
end
# @type var each_child: TSort::_EachChild[Symbol]
each_child = __skip__ = -> (name, &block) do
# @type var each_child: ^(Symbol) { (Symbol) -> void } -> void
each_child = -> (name, &block) do
if param = params.find {|p| p.name == name }
if b = param.upper_bound
b.free_variables.each do |tv|
Expand Down
Loading

0 comments on commit 62f61ac

Please sign in to comment.