steep 0.1.0.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +4 -0
  5. data/README.md +95 -0
  6. data/Rakefile +23 -0
  7. data/bin/console +14 -0
  8. data/bin/setup +8 -0
  9. data/bin/smoke_runner.rb +106 -0
  10. data/exe/steep +18 -0
  11. data/lib/steep.rb +33 -0
  12. data/lib/steep/annotation.rb +223 -0
  13. data/lib/steep/cli.rb +79 -0
  14. data/lib/steep/drivers/check.rb +141 -0
  15. data/lib/steep/errors.rb +207 -0
  16. data/lib/steep/interface.rb +280 -0
  17. data/lib/steep/parser.y +311 -0
  18. data/lib/steep/signature/class.rb +358 -0
  19. data/lib/steep/signature/errors.rb +78 -0
  20. data/lib/steep/signature/extension.rb +51 -0
  21. data/lib/steep/signature/interface.rb +48 -0
  22. data/lib/steep/source.rb +98 -0
  23. data/lib/steep/type_assignability.rb +362 -0
  24. data/lib/steep/type_construction.rb +993 -0
  25. data/lib/steep/type_name.rb +37 -0
  26. data/lib/steep/types.rb +4 -0
  27. data/lib/steep/types/any.rb +31 -0
  28. data/lib/steep/types/class.rb +27 -0
  29. data/lib/steep/types/instance.rb +27 -0
  30. data/lib/steep/types/merge.rb +32 -0
  31. data/lib/steep/types/name.rb +57 -0
  32. data/lib/steep/types/union.rb +42 -0
  33. data/lib/steep/types/var.rb +38 -0
  34. data/lib/steep/typing.rb +70 -0
  35. data/lib/steep/version.rb +3 -0
  36. data/manual/annotations.md +144 -0
  37. data/sig/signature.rbi +54 -0
  38. data/sig/types.rbi +43 -0
  39. data/smoke/and/a.rb +9 -0
  40. data/smoke/array/a.rb +22 -0
  41. data/smoke/block/a.rb +12 -0
  42. data/smoke/block/a.rbi +4 -0
  43. data/smoke/case/a.rb +20 -0
  44. data/smoke/class/a.rb +31 -0
  45. data/smoke/class/a.rbi +9 -0
  46. data/smoke/class/b.rb +7 -0
  47. data/smoke/class/c.rb +10 -0
  48. data/smoke/const/a.rb +30 -0
  49. data/smoke/dstr/a.rb +6 -0
  50. data/smoke/extension/a.rb +11 -0
  51. data/smoke/extension/a.rbi +8 -0
  52. data/smoke/extension/b.rb +12 -0
  53. data/smoke/extension/c.rb +9 -0
  54. data/smoke/hello/hello.rb +13 -0
  55. data/smoke/hello/hello.rbi +7 -0
  56. data/smoke/if/a.rb +20 -0
  57. data/smoke/implements/a.rb +14 -0
  58. data/smoke/implements/a.rbi +6 -0
  59. data/smoke/literal/a.rb +16 -0
  60. data/smoke/map/a.rb +5 -0
  61. data/smoke/method/a.rb +26 -0
  62. data/smoke/method/a.rbi +0 -0
  63. data/smoke/module/a.rb +21 -0
  64. data/smoke/module/a.rbi +7 -0
  65. data/smoke/module/b.rb +8 -0
  66. data/smoke/self/a.rb +23 -0
  67. data/smoke/self/a.rbi +4 -0
  68. data/smoke/super/a.rb +34 -0
  69. data/smoke/super/a.rbi +10 -0
  70. data/smoke/yield/a.rb +18 -0
  71. data/stdlib/builtin.rbi +89 -0
  72. data/steep.gemspec +32 -0
  73. metadata +214 -0
data/lib/steep/cli.rb ADDED
@@ -0,0 +1,79 @@
1
+ require 'optparse'
2
+
3
+ module Steep
4
+ class CLI
5
+ attr_reader :argv
6
+ attr_reader :stdout
7
+ attr_reader :stdin
8
+ attr_reader :stderr
9
+ attr_reader :command
10
+
11
+ def initialize(stdout:, stdin:, stderr:, argv:)
12
+ @stdout = stdout
13
+ @stdin = stdin
14
+ @stderr = stderr
15
+ @argv = argv
16
+ end
17
+
18
+ def self.available_commands
19
+ [:check]
20
+ end
21
+
22
+ def setup_global_options
23
+ true
24
+ end
25
+
26
+ def setup_command
27
+ @command = argv.shift&.to_sym
28
+ if CLI.available_commands.include?(@command)
29
+ true
30
+ else
31
+ stderr.puts "Unknown command: #{command}"
32
+ stderr.puts " available commands: #{CLI.available_commands.join(', ')}"
33
+ false
34
+ end
35
+ end
36
+
37
+ def run
38
+ setup_global_options or return 1
39
+ setup_command or return 1
40
+
41
+ __send__(:"process_#{command}")
42
+ end
43
+
44
+ def process_check
45
+ signature_dirs = []
46
+ verbose = false
47
+ no_builtin = false
48
+ dump_all_types = false
49
+ fallback_any_is_error = false
50
+
51
+ OptionParser.new do |opts|
52
+ opts.on("-I [PATH]") {|path| signature_dirs << Pathname(path) }
53
+ opts.on("--no-builtin") { no_builtin = true }
54
+ opts.on("--verbose") { verbose = true }
55
+ opts.on("--dump-all-types") { dump_all_types = true }
56
+ opts.on("--fallback-any-is-error") { fallback_any_is_error = true }
57
+ end.parse!(argv)
58
+
59
+ unless no_builtin
60
+ signature_dirs.unshift Pathname(__dir__).join("../../stdlib").realpath
61
+ end
62
+
63
+ if signature_dirs.empty?
64
+ signature_dirs << Pathname("sig")
65
+ end
66
+
67
+ source_paths = argv.map {|path| Pathname(path) }
68
+ if source_paths.empty?
69
+ source_paths << Pathname(".")
70
+ end
71
+
72
+ Drivers::Check.new(source_paths: source_paths, signature_dirs: signature_dirs, stdout: stdout, stderr: stderr).tap do |check|
73
+ check.verbose = verbose
74
+ check.dump_all_types = dump_all_types
75
+ check.fallback_any_is_error = fallback_any_is_error
76
+ end.run
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,141 @@
1
+ module Steep
2
+ module Drivers
3
+ class Check
4
+ attr_reader :source_paths
5
+ attr_reader :signature_dirs
6
+ attr_reader :stdout
7
+ attr_reader :stderr
8
+
9
+ attr_accessor :verbose
10
+ attr_accessor :accept_implicit_any
11
+ attr_accessor :dump_all_types
12
+ attr_accessor :fallback_any_is_error
13
+
14
+ attr_reader :labeling
15
+
16
+ def initialize(source_paths:, signature_dirs:, stdout:, stderr:)
17
+ @source_paths = source_paths
18
+ @signature_dirs = signature_dirs
19
+ @stdout = stdout
20
+ @stderr = stderr
21
+
22
+ self.verbose = false
23
+ self.accept_implicit_any = false
24
+ self.dump_all_types = false
25
+ self.fallback_any_is_error = false
26
+
27
+ @labeling = ASTUtils::Labeling.new
28
+ end
29
+
30
+ def run
31
+ assignability = TypeAssignability.new do |a|
32
+ each_interface do |signature|
33
+ a.add_signature(signature)
34
+ end
35
+ end
36
+
37
+ sources = []
38
+ each_ruby_source do |source|
39
+ sources << source
40
+ end
41
+
42
+ typing = Typing.new
43
+
44
+ sources.each do |source|
45
+ stdout.puts "Typechecking #{source.path}..." if verbose
46
+ annotations = source.annotations(block: source.node) || []
47
+
48
+ p annotations if verbose
49
+
50
+ construction = TypeConstruction.new(
51
+ assignability: assignability,
52
+ annotations: annotations,
53
+ source: source,
54
+ var_types: {},
55
+ self_type: nil,
56
+ block_context: nil,
57
+ module_context: TypeConstruction::ModuleContext.new(
58
+ instance_type: nil,
59
+ module_type: nil,
60
+ const_types: annotations.const_types
61
+ ),
62
+ method_context: nil,
63
+ typing: typing,
64
+ )
65
+ construction.synthesize(source.node)
66
+ end
67
+
68
+ if dump_all_types
69
+ lines = []
70
+
71
+ typing.nodes.each_value do |node|
72
+ begin
73
+ type = typing.type_of(node: node)
74
+ lines << [node.loc.expression.source_buffer.name, [node.loc.last_line,node.loc.last_column], [node.loc.first_line, node.loc.column], node, type]
75
+ rescue
76
+ lines << [node.loc.expression.source_buffer.name, [node.loc.last_line,node.loc.last_column], [node.loc.first_line, node.loc.column], node, nil]
77
+ end
78
+ end
79
+
80
+ lines.sort {|x,y| y <=> x }.reverse_each do |line|
81
+ source = line[3].loc.expression.source
82
+ stdout.puts "#{line[0]}:(#{line[2].join(",")}):(#{line[1].join(",")}):\t#{line[3].type}:\t#{line[4]}\t(#{source.split(/\n/).first})"
83
+ end
84
+ end
85
+
86
+ typing.errors.each do |error|
87
+ next if error.is_a?(Errors::FallbackAny) && !fallback_any_is_error
88
+ stdout.puts error.to_s
89
+ end
90
+ end
91
+
92
+ def each_interface
93
+ signature_dirs.each do |path|
94
+ if path.file?
95
+ stdout.puts "Loading signature #{path}..." if verbose
96
+ Parser.parse_signature(path.read).each do |interface|
97
+ yield interface
98
+ end
99
+ end
100
+
101
+ if path.directory?
102
+ each_file_in_dir(".rbi", path) do |file|
103
+ stdout.puts "Loading signature #{file}..." if verbose
104
+ Parser.parse_signature(file.read).each do |interface|
105
+ yield interface
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ def each_ruby_source
113
+ source_paths.each do |path|
114
+ if path.file?
115
+ stdout.puts "Loading Ruby program #{path}..." if verbose
116
+ yield Source.parse(path.read, path: path.to_s, labeling: labeling)
117
+ end
118
+
119
+ if path.directory?
120
+ each_file_in_dir(".rb", path) do |file|
121
+ stdout.puts "Loading Ruby program #{file}..." if verbose
122
+ yield Source.parse(file.read, path: file.to_s, labeling: labeling)
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ def each_file_in_dir(suffix, path, &block)
129
+ path.children.each do |child|
130
+ if child.directory?
131
+ each_file_in_dir(suffix, child, &block)
132
+ end
133
+
134
+ if child.file? && suffix == child.extname
135
+ yield child
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,207 @@
1
+ module Steep
2
+ module Errors
3
+ class Base
4
+ attr_reader :node
5
+
6
+ def initialize(node:)
7
+ @node = node
8
+ end
9
+
10
+ def location_to_str
11
+ "#{node.loc.expression.source_buffer.name}:#{node.loc.first_line}:#{node.loc.column}"
12
+ end
13
+ end
14
+
15
+ class IncompatibleAssignment < Base
16
+ attr_reader :lhs_type
17
+ attr_reader :rhs_type
18
+
19
+ def initialize(node:, lhs_type:, rhs_type:)
20
+ super(node: node)
21
+ @lhs_type = lhs_type
22
+ @rhs_type = rhs_type
23
+ end
24
+
25
+ def to_s
26
+ "#{location_to_str}: IncompatibleAssignment: lhs_type=#{lhs_type}, rhs_type=#{rhs_type}"
27
+ end
28
+ end
29
+
30
+ class ArgumentTypeMismatch < Base
31
+ attr_reader :type
32
+ attr_reader :method
33
+
34
+ def initialize(node:, type:, method:)
35
+ super(node: node)
36
+ @type = type
37
+ @method = method
38
+ end
39
+
40
+ def to_s
41
+ "#{location_to_str}: ArgumentTypeMismatch: type=#{type}, method=#{method}"
42
+ end
43
+ end
44
+
45
+ class NoMethod < Base
46
+ attr_reader :type
47
+ attr_reader :method
48
+
49
+ def initialize(node:, type:, method:)
50
+ super(node: node)
51
+ @type = type
52
+ @method = method
53
+ end
54
+
55
+ def to_s
56
+ "#{location_to_str}: NoMethodError: type=#{type}, method=#{method}"
57
+ end
58
+ end
59
+
60
+ class ReturnTypeMismatch < Base
61
+ attr_reader :expected
62
+ attr_reader :actual
63
+
64
+ def initialize(node:, expected:, actual:)
65
+ super(node: node)
66
+ @expected = expected
67
+ @actual = actual
68
+ end
69
+
70
+ def to_s
71
+ "#{location_to_str}: ReturnTypeMismatch: expected=#{expected}, actual=#{actual}"
72
+ end
73
+ end
74
+
75
+ class UnexpectedBlockGiven < Base
76
+ attr_reader :method
77
+ attr_reader :type
78
+
79
+ def initialize(node:, type:, method:)
80
+ super(node: node)
81
+ @type = type
82
+ @method = method
83
+ end
84
+ end
85
+
86
+ class BlockTypeMismatch < Base
87
+ attr_reader :expected
88
+ attr_reader :actual
89
+
90
+ def initialize(node:, expected:, actual:)
91
+ super(node: node)
92
+ @expected = expected
93
+ @actual = actual
94
+ end
95
+
96
+ def to_s
97
+ "#{location_to_str}: BlockTypeMismatch: expected=#{expected}, actual=#{actual}"
98
+ end
99
+ end
100
+
101
+ class BreakTypeMismatch < Base
102
+ attr_reader :expected
103
+ attr_reader :actual
104
+
105
+ def initialize(node:, expected:, actual:)
106
+ super(node: node)
107
+ @expected = expected
108
+ @actual = actual
109
+ end
110
+ end
111
+
112
+ class MethodParameterTypeMismatch < Base
113
+ def to_s
114
+ "#{location_to_str}: MethodParameterTypeMismatch: method=#{node.children[0]}"
115
+ end
116
+ end
117
+
118
+ class MethodBodyTypeMismatch < Base
119
+ attr_reader :expected
120
+ attr_reader :actual
121
+
122
+ def initialize(node:, expected:, actual:)
123
+ super(node: node)
124
+ @expected = expected
125
+ @actual = actual
126
+ end
127
+
128
+ def to_s
129
+ method = case node.type
130
+ when :def
131
+ node.children[0]
132
+ when :defs
133
+ prefix = node.children[0].type == :self ? "self" : "*"
134
+ "#{prefix}.#{node.children[1]}"
135
+ end
136
+ "#{location_to_str}: MethodBodyTypeMismatch: method=#{method}, expected=#{expected}, actual=#{actual}"
137
+ end
138
+ end
139
+
140
+ class UnexpectedYield < Base
141
+ def to_s
142
+ "#{location_to_str}: UnexpectedYield"
143
+ end
144
+ end
145
+
146
+ class UnexpectedSuper < Base
147
+ attr_reader :method
148
+
149
+ def initialize(node:, method:)
150
+ super(node: node)
151
+ @method = method
152
+ end
153
+
154
+ def to_s
155
+ "#{location_to_str}: UnexpectedSuper: method=#{method}"
156
+ end
157
+ end
158
+
159
+ class MethodDefinitionMissing < Base
160
+ attr_reader :module_name
161
+ attr_reader :kind
162
+ attr_reader :missing_method
163
+
164
+ def initialize(node:, module_name:, kind:, missing_method:)
165
+ super(node: node)
166
+ @module_name = module_name
167
+ @kind = kind
168
+ @missing_method = missing_method
169
+ end
170
+
171
+ def to_s
172
+ method = case kind
173
+ when :instance
174
+ "#{missing_method}"
175
+ when :module
176
+ "self.#{missing_method}"
177
+ end
178
+ "#{location_to_str}: MethodDefinitionMissing: module=#{module_name}, method=#{method}"
179
+ end
180
+ end
181
+
182
+ class UnexpectedDynamicMethod < Base
183
+ attr_reader :module_name
184
+ attr_reader :method_name
185
+
186
+ def initialize(node:, module_name:, method_name:)
187
+ @node = node
188
+ @module_name = module_name
189
+ @method_name = method_name
190
+ end
191
+
192
+ def to_s
193
+ "#{location_to_str}: UnexpectedDynamicMethod: module=#{module_name}, method=#{method_name}"
194
+ end
195
+ end
196
+
197
+ class FallbackAny < Base
198
+ def initialize(node:)
199
+ @node = node
200
+ end
201
+
202
+ def to_s
203
+ "#{location_to_str}: FallbackAny"
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,280 @@
1
+ module Steep
2
+ class Interface
3
+ class Params
4
+ attr_reader :required
5
+ attr_reader :optional
6
+ attr_reader :rest
7
+ attr_reader :required_keywords
8
+ attr_reader :optional_keywords
9
+ attr_reader :rest_keywords
10
+
11
+ def initialize(required:, optional:, rest:, required_keywords:, optional_keywords:, rest_keywords:)
12
+ @required = required
13
+ @optional = optional
14
+ @rest = rest
15
+ @required_keywords = required_keywords
16
+ @optional_keywords = optional_keywords
17
+ @rest_keywords = rest_keywords
18
+ end
19
+
20
+ def with(required: nil, optional: nil, rest: nil, required_keywords: nil, optional_keywords: nil, rest_keywords: nil)
21
+ self.class.new(required: required || self.required,
22
+ optional: optional || self.optional,
23
+ rest: rest || self.rest,
24
+ required_keywords: required_keywords || self.required_keywords,
25
+ optional_keywords: optional_keywords || self.optional_keywords,
26
+ rest_keywords: rest_keywords || self.rest_keywords)
27
+ end
28
+
29
+ def self.empty
30
+ new(required: [], optional: [], rest: nil, required_keywords: {}, optional_keywords: {}, rest_keywords: nil)
31
+ end
32
+
33
+ def ==(other)
34
+ other.is_a?(self.class) &&
35
+ other.required == required &&
36
+ other.optional == optional &&
37
+ other.rest == rest &&
38
+ other.required_keywords == required_keywords &&
39
+ other.optional_keywords == optional_keywords &&
40
+ other.rest_keywords == rest_keywords
41
+ end
42
+
43
+ def flat_unnamed_params
44
+ required.map {|p| [:required, p] } + optional.map {|p| [:optional, p] }
45
+ end
46
+
47
+ def flat_keywords
48
+ required_keywords.merge optional_keywords
49
+ end
50
+
51
+ def has_keywords?
52
+ !required_keywords.empty? || !optional_keywords.empty? || rest_keywords
53
+ end
54
+
55
+ def each_missing_argument(args)
56
+ required.size.times do |index|
57
+ if index >= args.size
58
+ yield index
59
+ end
60
+ end
61
+ end
62
+
63
+ def each_extra_argument(args)
64
+ return if rest
65
+
66
+ if has_keywords?
67
+ args = args.take(args.count - 1) if args.count > 0
68
+ end
69
+
70
+ args.size.times do |index|
71
+ if index >= required.count + optional.count
72
+ yield index
73
+ end
74
+ end
75
+ end
76
+
77
+ def each_missing_keyword(args)
78
+ return unless has_keywords?
79
+
80
+ keywords, rest = extract_keywords(args)
81
+
82
+ return unless rest.empty?
83
+
84
+ required_keywords.each do |keyword, _|
85
+ yield keyword unless keywords.key?(keyword)
86
+ end
87
+ end
88
+
89
+ def each_extra_keyword(args)
90
+ return unless has_keywords?
91
+ return if rest_keywords
92
+
93
+ keywords, rest = extract_keywords(args)
94
+
95
+ return unless rest.empty?
96
+
97
+ all_keywords = flat_keywords
98
+ keywords.each do |keyword, _|
99
+ yield keyword unless all_keywords.key?(keyword)
100
+ end
101
+ end
102
+
103
+ def extract_keywords(args)
104
+ last_arg = args.last
105
+
106
+ keywords = {}
107
+ rest = []
108
+
109
+ if last_arg&.type == :hash
110
+ last_arg.children.each do |element|
111
+ case element.type
112
+ when :pair
113
+ if element.children[0].type == :sym
114
+ name = element.children[0].children[0]
115
+ keywords[name] = element.children[1]
116
+ end
117
+ when :kwsplat
118
+ rest << element.children[0]
119
+ end
120
+ end
121
+ end
122
+
123
+ [keywords, rest]
124
+ end
125
+
126
+ def each_type()
127
+ if block_given?
128
+ flat_unnamed_params.each do |(_, type)|
129
+ yield type
130
+ end
131
+ flat_keywords.each do |_, type|
132
+ yield type
133
+ end
134
+ rest and yield rest
135
+ rest_keywords and yield rest_keywords
136
+ else
137
+ enum_for :each_type
138
+ end
139
+ end
140
+
141
+ def closed?
142
+ required.all?(&:closed?) && optional.all?(&:closed?) && (!rest || rest.closed?) && required_keywords.values.all?(&:closed?) && optional_keywords.values.all?(&:closed?) && (!rest_keywords || rest_keywords.closed?)
143
+ end
144
+
145
+ def substitute(klass:, instance:, params:)
146
+ self.class.new(required: required.map {|t| t.substitute(klass: klass, instance: instance, params: params) },
147
+ optional: optional.map {|t| t.substitute(klass: klass, instance: instance, params: params) },
148
+ rest: rest&.substitute(klass: klass, instance: instance, params: params),
149
+ required_keywords: required_keywords.transform_values {|t| t.substitute(klass: klass, instance: instance, params: params) },
150
+ optional_keywords: optional_keywords.transform_values {|t| t.substitute(klass: klass, instance: instance, params: params) },
151
+ rest_keywords: rest_keywords&.substitute(klass: klass, instance: instance, params: params) )
152
+ end
153
+
154
+ def size
155
+ required.size + optional.size + (rest ? 1 : 0) + required_keywords.size + optional_keywords.size + (rest_keywords ? 1 : 0)
156
+ end
157
+ end
158
+
159
+ class MethodType
160
+ attr_reader :type_params
161
+ attr_reader :params
162
+ attr_reader :block
163
+ attr_reader :return_type
164
+
165
+ NONE = Object.new
166
+
167
+ def initialize(type_params:, params:, block:, return_type:)
168
+ @type_params = type_params
169
+ @params = params
170
+ @block = block
171
+ @return_type = return_type
172
+ end
173
+
174
+ def updated(type_params: NONE, params: NONE, block: NONE, return_type: NONE)
175
+ self.class.new(type_params: type_params.equal?(NONE) ? self.type_params : type_params,
176
+ params: params.equal?(NONE) ? self.params : params,
177
+ block: block.equal?(NONE) ? self.block : block,
178
+ return_type: return_type.equal?(NONE) ? self.return_type : return_type)
179
+ end
180
+
181
+ def ==(other)
182
+ other.is_a?(self.class) &&
183
+ other.params == params &&
184
+ other.block == block &&
185
+ other.return_type == return_type
186
+ end
187
+
188
+ def closed?
189
+ params.closed? && (!block || block.closed?) && return_type.closed?
190
+ end
191
+
192
+ def substitute(klass:, instance:, params:)
193
+ self.class.new(type_params: type_params,
194
+ params: self.params.substitute(klass: klass, instance: instance, params: params),
195
+ block: block&.substitute(klass: klass, instance: instance, params: params),
196
+ return_type: return_type.substitute(klass: klass, instance: instance, params: params))
197
+ end
198
+
199
+ def instantiate(subst:)
200
+ raise "Open method type cannot be instantiated" unless closed?
201
+
202
+ self.class.new(type_params: type_params.reject {|param| subst.key?(param) },
203
+ params: self.params.substitute(klass: nil, instance: nil, params: subst),
204
+ block: block&.substitute(klass: nil, instance: nil, params: subst),
205
+ return_type: return_type.substitute(klass: nil, instance: nil, params: subst))
206
+ end
207
+ end
208
+
209
+ class Block
210
+ attr_reader :params
211
+ attr_reader :return_type
212
+
213
+ def initialize(params:, return_type:)
214
+ @params = params
215
+ @return_type = return_type
216
+ end
217
+
218
+ def ==(other)
219
+ other.is_a?(self.class) && other.params == params && other.return_type == return_type
220
+ end
221
+
222
+ def closed?
223
+ params.closed? && return_type.closed?
224
+ end
225
+
226
+ def substitute(klass:, instance:, params:)
227
+ self.class.new(params: self.params.substitute(klass: klass, instance: instance, params: params),
228
+ return_type: return_type.substitute(klass: klass, instance: instance, params: params))
229
+ end
230
+ end
231
+
232
+ class Method
233
+ attr_reader :super_method
234
+ attr_reader :types
235
+
236
+ def initialize(types:, super_method:)
237
+ @types = types
238
+ @super_method = super_method
239
+ end
240
+
241
+ def ==(other)
242
+ other.is_a?(Method) && other.types == types && other.super_method == super_method
243
+ end
244
+
245
+ def closed?
246
+ types.all?(&:closed?)
247
+ end
248
+
249
+ def substitute(klass:, instance:, params:)
250
+ self.class.new(
251
+ types: types.map {|type| type.substitute(klass: klass, instance: instance, params: params) },
252
+ super_method: super_method&.substitute(klass: klass, instance: instance, params: params)
253
+ )
254
+ end
255
+
256
+ def map_types()
257
+ self.class.new(
258
+ types: types.map {|type| yield(type) },
259
+ super_method: super_method
260
+ )
261
+ end
262
+ end
263
+
264
+ attr_reader :name
265
+ attr_reader :methods
266
+
267
+ def initialize(name:, methods:)
268
+ @name = name
269
+ @methods = methods
270
+ end
271
+
272
+ def closed?
273
+ methods.values.all?(&:closed?)
274
+ end
275
+
276
+ def ==(other)
277
+ other.is_a?(self.class) && other.name == name && other.methods == methods
278
+ end
279
+ end
280
+ end