Class: RBS::TypeAliasRegularity

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

Overview

TypeAliasRegularity validates if a type alias is regular or not.

Generic and recursive type alias cannot be polymorphic in their definitions.

type foo[T] = Integer
            | foo[T]?     # Allowed. The type argument of `foo` doesn't change.

type bar[T] = Integer
            | foo[T]
            | foo[Array[T]]  # Allowed. There are two type arguments `T` and `Array[T]` of `foo`, but it's not definition of `foo`.

type baz[T] = Integer
            | baz[Array[T]]  # Error. Recursive definition of `baz` has different type argument from the definition.

The #nonregular? method can be used to test if given type name is regular or not.

validator = RBS::TypeAliasRegularity.validate(env: env)

validator.nonregular?(TypeName.parse("::foo"))    # => nil
validator.nonregular?(TypeName.parse("::bar"))    # => nil
validator.nonregular?(TypeName.parse("::baz"))    # => TypeAliasRegularity::Diagnostic

A special case is when the type argument is untyped.

type foo[T] = Integer | foo[untyped]    # This is allowed.

Defined Under Namespace

Classes: Diagnostic

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(env:) ⇒ TypeAliasRegularity

Returns a new instance of TypeAliasRegularity.

Parameters:



16
17
18
19
20
# File 'lib/rbs/type_alias_regularity.rb', line 16

def initialize(env:)
  @env = env
  @builder = DefinitionBuilder.new(env: env)
  @diagnostics = {}
end

Instance Attribute Details

#builderDefinitionBuilder (readonly)

Returns the value of attribute builder.

Returns:



14
15
16
# File 'lib/rbs/type_alias_regularity.rb', line 14

def builder
  @builder
end

#diagnosticsHash[TypeName, Diagnostic] (readonly)

Diagnostics of each type aliases. The type names are normalized.

Returns:



42
43
44
# File 'sig/type_alias_regularity.rbs', line 42

def diagnostics
  @diagnostics
end

#envEnvironment (readonly)

Returns the value of attribute env.

Returns:



14
15
16
# File 'lib/rbs/type_alias_regularity.rb', line 14

def env
  @env
end

Class Method Details

.validate(env:) ⇒ TypeAliasRegularity

Returns new instance which already run #validate.

Parameters:

Returns:



69
70
71
72
73
# File 'sig/type_alias_regularity.rbs', line 69

def self.validate(env:)
  self.new(env: env).tap do |validator|
    validator.validate()
  end
end

Instance Method Details

#build_alias_type(name) ⇒ Types::Alias?

Returns alias type for given type name, if the alias is generic. Returns nil if the type alias is not generic.

Parameters:

Returns:



89
90
91
92
93
94
95
# File 'sig/type_alias_regularity.rbs', line 89

def build_alias_type(name)
  entry = env.type_alias_decls[name] or return
  unless entry.decl.type_params.empty?
    as = entry.decl.type_params.each.map {|param| Types::Variable.new(name: param.name, location: nil) }
    Types::Alias.new(name: name, args: as, location: nil)
  end
end

#compatible_args?(args1, args2) ⇒ Boolean

Parameters:

  • (Array[Types::t])
  • (Array[Types::t])

Returns:

  • (Boolean)


69
70
71
72
73
74
75
76
77
# File 'lib/rbs/type_alias_regularity.rb', line 69

def compatible_args?(args1, args2)
  if args1.size == args2.size
    args1.zip(args2).all? do |t1, t2|
      t1.is_a?(Types::Bases::Any) ||
        t2.is_a?(Types::Bases::Any) ||
        t1 == t2
    end
  end
end

#each_alias_type(type) {|arg0| ... } ⇒ void

This method returns an undefined value.

Parameters:

  • (Types::t)

Yields:

Yield Parameters:

Yield Returns:

  • (void)


110
111
112
113
114
115
116
117
118
# File 'lib/rbs/type_alias_regularity.rb', line 110

def each_alias_type(type, &block)
  if type.is_a?(RBS::Types::Alias)
    yield type
  end

  type.each_type do |ty|
    each_alias_type(ty, &block)
  end
end

#each_mutual_alias_defs {|arg0| ... } ⇒ void

This method returns an undefined value.

Yields set of normalized type names

Yields:

Yield Parameters:

Yield Returns:

  • (void)


96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'sig/type_alias_regularity.rbs', line 96

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

  # @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|
        if normalized = env.normalize_type_name?(ty.name)
          block[normalized]
        end
      end
    end
  end

  TSort.each_strongly_connected_component(each_node, each_child) do |names|
    yield Set.new(names)
  end
end

#nonregular?(type_name) ⇒ Diagnostic?

Returns Diagnostic instance if the alias type is nonregular. Returns nil if the alias type is regular.

Normalizes the given type name automatically.

Parameters:

Returns:



78
79
80
# File 'sig/type_alias_regularity.rbs', line 78

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

#validatevoid

This method returns an undefined value.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/rbs/type_alias_regularity.rb', line 22

def validate
  diagnostics.clear

  each_mutual_alias_defs do |names|
    # Find the first generic type alias in strongly connected component.
    # This is to skip the regularity check when the alias is not generic.
    names.each do |name|
      # @type break: nil
      if type = build_alias_type(name)
        # Running validation only once from the first generic type is enough, because they are mutual recursive definition.
        validate_alias_type(type, names, {})
        break
      end
    end
  end
end

#validate_alias_type(alias_type, names, types) ⇒ void

This method returns an undefined value.

Parameters:



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/rbs/type_alias_regularity.rb', line 39

def validate_alias_type(alias_type, names, types)
  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_name] ||=
          Diagnostic.new(type_name: alias_type.name, nonregular_type: alias_type)
      end

      return
    else
      types[alias_type.name] = alias_type
    end

    expanded = builder.expand_alias2(alias_name, alias_type.args)
    each_alias_type(expanded) do |at|
      validate_alias_type(at, names, types)
    end
  end
end