Module: T::Private::Abstract::Validate

Defined in:
lib/types/private/abstract/validate.rb

Overview

typed: true

Constant Summary collapse

Abstract =
T::Private::Abstract
AbstractUtils =
T::AbstractUtils
Methods =
T::Private::Methods
SignatureValidation =
T::Private::Methods::SignatureValidation

Class Method Summary collapse

Class Method Details

.validate_abstract_module(mod) ⇒ Object



10
11
12
13
# File 'lib/types/private/abstract/validate.rb', line 10

def self.validate_abstract_module(mod)
  type = Abstract::Data.get(mod, :abstract_type)
  validate_interface(mod) if type == :interface
end

.validate_subclass(mod) ⇒ Object

Validates a class/module with an abstract class/module as an ancestor. This must be called after all methods on ‘mod` have been defined.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/types/private/abstract/validate.rb', line 17

def self.validate_subclass(mod)
  can_have_abstract_methods = !T::Private::Abstract::Data.get(mod, :can_have_abstract_methods)
  unimplemented_methods = []

  T::AbstractUtils.declared_abstract_methods_for(mod).each do |abstract_method|
    implementation_method = mod.instance_method(abstract_method.name)
    if AbstractUtils.abstract_method?(implementation_method)
      # Note that when we end up here, implementation_method might not be the same as
      # abstract_method; the latter could've been overridden by another abstract method. In either
      # case, if we have a concrete definition in an ancestor, that will end up as the effective
      # implementation (see CallValidation.wrap_method_if_needed), so that's what we'll validate
      # against.
      implementation_method = T.unsafe(nil)
      mod.ancestors.each do |ancestor|
        if ancestor.instance_methods.include?(abstract_method.name)
          method = ancestor.instance_method(abstract_method.name)
          T::Private::Methods.maybe_run_sig_block_for_method(method)
          if !T::AbstractUtils.abstract_method?(method)
            implementation_method = method
            break
          end
        end
      end
      if !implementation_method
        # There's no implementation
        if can_have_abstract_methods
          unimplemented_methods << describe_method(abstract_method)
        end
        next # Nothing to validate
      end
    end

    implementation_signature = Methods.signature_for_method(implementation_method)
    # When a signature exists and the method is defined directly on `mod`, we skip the validation
    # here, because it will have already been done when the method was defined (by
    # T::Private::Methods._on_method_added).
    next if implementation_signature&.owner == mod

    # We validate the remaining cases here: (a) methods defined directly on `mod` without a
    # signature and (b) methods from ancestors (note that these ancestors can come before or
    # after the abstract module in the inheritance chain -- the former coming from
    # walking `mod.ancestors` above).
    abstract_signature = Methods.signature_for_method(abstract_method)
    # We allow implementation methods to be defined without a signature.
    # In that case, get its untyped signature.
    implementation_signature ||= Methods::Signature.new_untyped(
      method: implementation_method,
      mode: Methods::Modes.override
    )
    SignatureValidation.validate_override_shape(implementation_signature, abstract_signature)
    SignatureValidation.validate_override_types(implementation_signature, abstract_signature)
  end

  method_type = mod.singleton_class? ? "class" : "instance"
  if !unimplemented_methods.empty?
    raise "Missing implementation for abstract #{method_type} method(s) in #{mod}:\n" \
          "#{unimplemented_methods.join("\n")}\n" \
          "If #{mod} is meant to be an abstract class/module, you can call " \
          "`abstract!` or `interface!`. Otherwise, you must implement the method(s)."
  end
end