Class: RBS::Resolver::TypeNameResolver

Inherits:
Object
  • Object
show all
Defined in:
sig/resolver/type_name_resolver.rbs,
lib/rbs/resolver/type_name_resolver.rb

Overview

TypeNameResolver resolves given relative type name to absolute type name under a module nesting context.

The type name resolution doesn't take account of ancestors of modules. It just ignores included modules and super classes.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(all_names, aliases) ⇒ TypeNameResolver

Returns a new instance of TypeNameResolver.

Parameters:



10
11
12
13
14
# File 'lib/rbs/resolver/type_name_resolver.rb', line 10

def initialize(all_names, aliases)
  @all_names = all_names
  @aliases = aliases
  @cache = {}
end

Instance Attribute Details

#aliasesHash[TypeName, [TypeName, context]] (readonly)

Returns the value of attribute aliases.

Returns:



8
9
10
# File 'lib/rbs/resolver/type_name_resolver.rb', line 8

def aliases
  @aliases
end

#all_namesSet[TypeName] (readonly)

Returns the value of attribute all_names.

Returns:



6
7
8
# File 'lib/rbs/resolver/type_name_resolver.rb', line 6

def all_names
  @all_names
end

#cacheHash[context, Hash[TypeName, TypeName?]] (readonly)

Returns the value of attribute cache.

Returns:



7
8
9
# File 'lib/rbs/resolver/type_name_resolver.rb', line 7

def cache
  @cache
end

Class Method Details

.build(env) ⇒ instance

Parameters:

Returns:

  • (instance)


16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/rbs/resolver/type_name_resolver.rb', line 16

def self.build(env)
  all_names = Set.new #: Set[TypeName]

  all_names.merge(env.class_decls.keys)
  all_names.merge(env.interface_decls.keys)
  all_names.merge(env.type_alias_decls.keys)

  aliases = {} #: Hash[TypeName, [TypeName, context]]

  env.class_alias_decls.each do |name, entry|
    aliases[name] = [entry.decl.old_name, entry.context]
  end

  new(all_names, aliases)
end

Instance Method Details

#aliased_name?(type_name) ⇒ TypeName?

Returns the type name if it is an alias (not normalized)

Parameters:

Returns:



41
42
43
44
45
# File 'sig/resolver/type_name_resolver.rbs', line 41

def aliased_name?(type_name)
  if aliases.key?(type_name)
    type_name
  end
end

#has_type_name?(full_name) ⇒ TypeName?

Returns the type name if it exists in all_names (normalized)

Parameters:

Returns:



37
38
39
40
41
# File 'sig/resolver/type_name_resolver.rbs', line 37

def has_type_name?(full_name)
  if all_names.include?(full_name)
    full_name
  end
end

#normalize_namespace(type_name, rhs, context, visited) ⇒ TypeName, ...

Parameters:

Returns:



119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/rbs/resolver/type_name_resolver.rb', line 119

def normalize_namespace(type_name, rhs, context, visited)
  if visited.include?(type_name)
    # Cycle detected
    return false
  end

  visited << type_name

  begin
    resolve_namespace0(rhs, context, visited)
  ensure
    visited.delete(type_name)
  end
end

#resolve(type_name, context:) ⇒ TypeName?

Translates given type name to absolute type name

Returns nil if cannot find associated type name. Module names in the type name are normalized.

Parameters:

Returns:



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'sig/resolver/type_name_resolver.rbs', line 18

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

  try_cache(type_name, context) do
    if type_name.class?
      resolve_namespace0(type_name, context, Set.new) || nil
    else
      namespace = type_name.namespace

      if namespace.empty?
        resolve_type_name(type_name.name, context)
      else
        if namespace = resolve_namespace0(namespace.to_type_name, context, Set.new)
          type_name = TypeName[namespace.to_namespace, type_name.name]
          has_type_name?(type_name)
        end
      end
    end
  end
end

#resolve_head_namespace(head, context) ⇒ TypeName?

Translates the head module name in the context and returns an absolute type name

Returns nil if cannot find associated type name. The returned namespace may be an alias

Parameters:

  • (Symbol)
  • (context)

Returns:



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'sig/resolver/type_name_resolver.rbs', line 50

def resolve_head_namespace(head, context)
  if context
    outer, inner = context
    case inner
    when false
      resolve_head_namespace(head, outer)
    when TypeName
      has_type_name?(inner) or raise "Context must be normalized: #{inner.inspect}"
      type_name = TypeName[inner.to_namespace, head]
      has_type_name?(type_name) || aliased_name?(type_name) || resolve_head_namespace(head, outer)
    end
  else
    type_name = TypeName[Namespace.root, head]
    has_type_name?(type_name) || aliased_name?(type_name)
  end
end

#resolve_namespace(type_name, context:) ⇒ TypeName, ...

Translates given type name to absolute type name

Returns false if a cycle is detected while resolving aliases. Returns nil if the type name cannot be resolved.

Parameters:

Returns:



25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'sig/resolver/type_name_resolver.rbs', line 25

def resolve_namespace(type_name, context:)
  if type_name.absolute? && has_type_name?(type_name)
    return type_name
  end

  unless type_name.class?
    raise "Type name must be a class name: #{type_name}"
  end

  try_cache(type_name, context) do
    ns = resolve_namespace0(type_name, context, Set.new) or return ns
  end
end

#resolve_namespace0(type_name, context, visited) ⇒ TypeName, ...

Parameters:

Returns:



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/rbs/resolver/type_name_resolver.rb', line 134

def resolve_namespace0(type_name, context, visited)
  head, *tail = [*type_name.namespace.path, type_name.name]

  head = head #: Symbol

  head =
    if type_name.absolute?
      root_name = TypeName[Namespace.root, head]
      has_type_name?(root_name) || aliased_name?(root_name)
    else
      resolve_head_namespace(head, context)
    end

  if head
    if (rhs, context = aliases.fetch(head, nil))
      head = normalize_namespace(head, rhs, context, visited) or return head
    end

    tail.inject(head) do |namespace, name|
      type_name = TypeName[namespace.to_namespace, name]
      case
      when has_type_name?(type_name)
        type_name
      when (rhs, context = aliases.fetch(type_name, nil))
        m = normalize_namespace(type_name, rhs, context, visited) or return m
      else
        return nil
      end
    end
  end
end

#resolve_type_name(type_name, context) ⇒ TypeName?

Resolves the type name in the given context

Parameters:

  • (Symbol)
  • (context)

Returns:



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'sig/resolver/type_name_resolver.rbs', line 54

def resolve_type_name(type_name, context)
  if context
    outer, inner = context
    case inner
    when false
      resolve_type_name(type_name, outer)
    else
      has_type_name?(inner) or raise "Context must be normalized: #{inner.inspect}"
      has_type_name?(TypeName[inner.to_namespace, type_name]) || resolve_type_name(type_name, outer)
    end
  else
    type_name = TypeName[Namespace.root, type_name]
    has_type_name?(type_name)
  end
end

#try_cache(type_name, context) { ... } ⇒ TypeName?

Parameters:

Yields:

Yield Returns:

Returns:



32
33
34
35
# File 'lib/rbs/resolver/type_name_resolver.rb', line 32

def try_cache(type_name, context)
  inner = cache[context] ||= {}
  inner.fetch(type_name) { inner[type_name] = yield }
end