-
-
Notifications
You must be signed in to change notification settings - Fork 9.3k
/
validate.rb
128 lines (113 loc) 路 5.28 KB
/
validate.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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
122
123
124
125
126
127
128
# frozen_string_literal: true
# typed: true
module T::Private::Abstract::Validate
Abstract = T::Private::Abstract
AbstractUtils = T::AbstractUtils
Methods = T::Private::Methods
SignatureValidation = T::Private::Methods::SignatureValidation
def self.validate_abstract_module(mod)
type = Abstract::Data.get(mod, :abstract_type)
validate_interface(mod) if type == :interface
end
# Validates a class/module with an abstract class/module as an ancestor. This must be called
# after all methods on `mod` have been defined.
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
private_class_method def self.validate_interface_all_abstract(mod, method_names)
violations = method_names.map do |method_name|
method = mod.instance_method(method_name)
if !AbstractUtils.abstract_method?(method)
describe_method(method, show_owner: false)
end
end.compact
if !violations.empty?
raise "`#{mod}` is declared as an interface, but the following methods are not declared " \
"with `abstract`:\n#{violations.join("\n")}"
end
end
private_class_method def self.validate_interface(mod)
interface_methods = T::Utils.methods_excluding_object(mod)
validate_interface_all_abstract(mod, interface_methods)
validate_interface_all_public(mod, interface_methods)
end
private_class_method def self.validate_interface_all_public(mod, method_names)
violations = method_names.map do |method_name|
if !mod.public_method_defined?(method_name)
describe_method(mod.instance_method(method_name), show_owner: false)
end
end.compact
if !violations.empty?
raise "All methods on an interface must be public. If you intend to have non-public " \
"methods, declare your class/module using `abstract!` instead of `interface!`. " \
"The following methods on `#{mod}` are not public: \n#{violations.join("\n")}"
end
end
private_class_method def self.describe_method(method, show_owner: true)
loc = if method.source_location
method.source_location.join(':')
else
"<unknown location>"
end
owner = if show_owner
" declared in #{method.owner}"
else
""
end
" * `#{method.name}`#{owner} at #{loc}"
end
end