-
-
Notifications
You must be signed in to change notification settings - Fork 9.3k
/
sealed.rb
91 lines (80 loc) 路 3.12 KB
/
sealed.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
# frozen_string_literal: true
# typed: false
module T::Private::Sealed
module NoInherit
def inherited(child)
super
caller_loc = T::Private::CallerUtils.find_caller {|loc| loc.base_label != 'inherited'}
T::Private::Sealed.validate_inheritance(caller_loc, self, child, 'inherited')
@sorbet_sealed_module_all_subclasses << child
end
def sealed_subclasses
@sorbet_sealed_module_all_subclasses_set ||= # rubocop:disable Naming/MemoizedInstanceVariableName
begin
require 'set'
Set.new(@sorbet_sealed_module_all_subclasses).freeze
end
end
end
module NoIncludeExtend
def included(child)
super
caller_loc = T::Private::CallerUtils.find_caller {|loc| !loc.to_s.match(/in `included'$/)}
T::Private::Sealed.validate_inheritance(caller_loc, self, child, 'included')
@sorbet_sealed_module_all_subclasses << child
end
def extended(child)
super
caller_loc = T::Private::CallerUtils.find_caller {|loc| !loc.to_s.match(/in `extended'$/)}
T::Private::Sealed.validate_inheritance(caller_loc, self, child, 'extended')
@sorbet_sealed_module_all_subclasses << child
end
def sealed_subclasses
# this will freeze the set so that you can never get into a
# state where you use the subclasses list and then something
# else will add to it
@sorbet_sealed_module_all_subclasses_set ||= # rubocop:disable Naming/MemoizedInstanceVariableName
begin
require 'set'
Set.new(@sorbet_sealed_module_all_subclasses).freeze
end
end
end
def self.declare(mod, decl_file)
if !mod.is_a?(Module)
raise "#{mod} is not a class or module and cannot be declared `sealed!`"
end
if sealed_module?(mod)
raise "#{mod} was already declared `sealed!` and cannot be re-declared `sealed!`"
end
if T::Private::Final.final_module?(mod)
raise "#{mod} was already declared `final!` and cannot be declared `sealed!`"
end
mod.extend(mod.is_a?(Class) ? NoInherit : NoIncludeExtend)
if !decl_file
raise "Couldn't determine declaration file for sealed class."
end
mod.instance_variable_set(:@sorbet_sealed_module_decl_file, decl_file)
mod.instance_variable_set(:@sorbet_sealed_module_all_subclasses, [])
end
def self.sealed_module?(mod)
mod.instance_variable_defined?(:@sorbet_sealed_module_decl_file)
end
def self.validate_inheritance(caller_loc, parent, child, verb)
this_file = caller_loc&.path
decl_file = parent.instance_variable_get(:@sorbet_sealed_module_decl_file) if sealed_module?(parent)
if !this_file
raise "Could not use backtrace to determine file for #{verb} child #{child}"
end
if !decl_file
raise "#{parent} does not seem to be a sealed module (#{verb} by #{child})"
end
if !this_file.start_with?(decl_file)
whitelist = T::Configuration.sealed_violation_whitelist
if !whitelist.nil? && whitelist.any? {|pattern| this_file =~ pattern}
return
end
raise "#{parent} was declared sealed and can only be #{verb} in #{decl_file}, not #{this_file}"
end
end
end