steep 0.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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