jade-lang 0.9.0 → 0.10.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +400 -12
- data/docs/interop.md +18 -1
- data/docs/stdlib.md +3 -1
- data/docs/syntax.md +96 -0
- data/lib/jade/api.rb +9 -0
- data/lib/jade/ast.rb +37 -1
- data/lib/jade/cli/eject.rb +132 -0
- data/lib/jade/cli.rb +2 -0
- data/lib/jade/codegen/boundary/specialized/list.rb +11 -7
- data/lib/jade/codegen/boundary/specialized/maybe.rb +2 -2
- data/lib/jade/codegen/boundary/specialized/record.rb +19 -4
- data/lib/jade/codegen/boundary/specialized/scalar.rb +2 -2
- data/lib/jade/codegen/boundary/specialized.rb +13 -5
- data/lib/jade/codegen/boundary.rb +12 -2
- data/lib/jade/codegen/context.rb +17 -0
- data/lib/jade/codegen/error.rb +1 -0
- data/lib/jade/codegen/function_call.rb +23 -7
- data/lib/jade/codegen/function_declaration.rb +25 -13
- data/lib/jade/codegen/helpers.rb +27 -4
- data/lib/jade/codegen/implementation.rb +2 -2
- data/lib/jade/codegen/inline.rb +1 -1
- data/lib/jade/codegen/inlines.rb +6 -1
- data/lib/jade/codegen/names.rb +31 -0
- data/lib/jade/codegen/pattern/constructor.rb +2 -2
- data/lib/jade/codegen/transforms/fold_shape.rb +1 -1
- data/lib/jade/codegen.rb +76 -15
- data/lib/jade/compiler.rb +2 -0
- data/lib/jade/entry.rb +1 -1
- data/lib/jade/formatter/calls.rb +68 -11
- data/lib/jade/formatter/collections.rb +4 -4
- data/lib/jade/formatter/declarations.rb +13 -15
- data/lib/jade/formatter/helper.rb +20 -0
- data/lib/jade/formatter/infix_application.rb +19 -4
- data/lib/jade/formatter/lambda.rb +24 -12
- data/lib/jade/formatter/pattern.rb +3 -0
- data/lib/jade/frontend/desugaring/placeholder.rb +40 -17
- data/lib/jade/frontend/desugaring.rb +29 -9
- data/lib/jade/frontend/fixity_fixer.rb +6 -4
- data/lib/jade/frontend/forward_declaration/error/alias_expansion.rb +23 -0
- data/lib/jade/frontend/forward_declaration/error/duplicate_constructor_name.rb +45 -0
- data/lib/jade/frontend/forward_declaration/error/duplicate_type_name.rb +42 -0
- data/lib/jade/frontend/forward_declaration/error.rb +3 -0
- data/lib/jade/frontend/forward_declaration/helper.rb +58 -0
- data/lib/jade/frontend/forward_declaration/import_declaration.rb +4 -0
- data/lib/jade/frontend/forward_declaration/interface_declaration.rb +6 -0
- data/lib/jade/frontend/forward_declaration/module.rb +4 -0
- data/lib/jade/frontend/forward_declaration/struct_declaration.rb +10 -1
- data/lib/jade/frontend/forward_declaration/type_alias_declaration.rb +37 -0
- data/lib/jade/frontend/forward_declaration/type_declaration.rb +10 -1
- data/lib/jade/frontend/forward_declaration.rb +2 -0
- data/lib/jade/frontend/pattern_analysis/exhaustiveness.rb +47 -43
- data/lib/jade/frontend/pattern_analysis/matrix.rb +28 -219
- data/lib/jade/frontend/pattern_analysis/pattern.rb +132 -0
- data/lib/jade/frontend/pattern_analysis/redundancy.rb +24 -0
- data/lib/jade/frontend/pattern_analysis/signature.rb +220 -0
- data/lib/jade/frontend/pattern_analysis/usefulness.rb +72 -0
- data/lib/jade/frontend/pattern_analysis/witnesses.rb +73 -0
- data/lib/jade/frontend/pattern_analysis.rb +5 -38
- data/lib/jade/frontend/semantic_analysis/error/implementation_on_alias.rb +24 -0
- data/lib/jade/frontend/semantic_analysis/error/no_base_case.rb +45 -0
- data/lib/jade/frontend/semantic_analysis/error/placeholder_not_allowed.rb +15 -3
- data/lib/jade/frontend/semantic_analysis/error/recursive_type_alias.rb +24 -0
- data/lib/jade/frontend/semantic_analysis/error.rb +3 -0
- data/lib/jade/frontend/semantic_analysis/helper.rb +31 -0
- data/lib/jade/frontend/semantic_analysis/implementation.rb +13 -0
- data/lib/jade/frontend/semantic_analysis/inhabitedness.rb +90 -0
- data/lib/jade/frontend/semantic_analysis/interop_import_declaration.rb +28 -16
- data/lib/jade/frontend/semantic_analysis/keyed_call.rb +21 -5
- data/lib/jade/frontend/semantic_analysis/pattern_range.rb +16 -0
- data/lib/jade/frontend/semantic_analysis/struct_declaration.rb +1 -0
- data/lib/jade/frontend/semantic_analysis/type_alias_declaration/cycle_detection.rb +95 -0
- data/lib/jade/frontend/semantic_analysis/type_alias_declaration.rb +48 -0
- data/lib/jade/frontend/semantic_analysis/type_declaration.rb +1 -0
- data/lib/jade/frontend/semantic_analysis.rb +5 -0
- data/lib/jade/frontend/type_checking/canonicalize.rb +1 -0
- data/lib/jade/frontend/type_checking/cascade.rb +90 -0
- data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +10 -0
- data/lib/jade/frontend/type_checking/constraints.rb +18 -3
- data/lib/jade/frontend/type_checking/error/display.rb +49 -0
- data/lib/jade/frontend/type_checking/error/division_by_zero.rb +31 -0
- data/lib/jade/frontend/type_checking/error/empty_range_pattern.rb +25 -0
- data/lib/jade/frontend/type_checking/error/function_body_type_mismatch.rb +4 -3
- data/lib/jade/frontend/type_checking/error/function_call_type_mismatch.rb +108 -5
- data/lib/jade/frontend/type_checking/error/if_branch_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/if_branches_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/if_condition_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/implementation_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/list_item_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/missing_patterns.rb +20 -4
- data/lib/jade/frontend/type_checking/error/pattern_type_mismatch.rb +7 -1
- data/lib/jade/frontend/type_checking/error/range_pattern_type.rb +29 -0
- data/lib/jade/frontend/type_checking/error/record_access_type_mismatch.rb +2 -2
- data/lib/jade/frontend/type_checking/error/recursive_derivation.rb +25 -0
- data/lib/jade/frontend/type_checking/error/type_mismatch.rb +10 -2
- data/lib/jade/frontend/type_checking/error/unreachable_branch.rb +22 -0
- data/lib/jade/frontend/type_checking/error.rb +6 -0
- data/lib/jade/frontend/type_checking/inference/assign.rb +1 -1
- data/lib/jade/frontend/type_checking/inference/case_of.rb +21 -4
- data/lib/jade/frontend/type_checking/inference/division.rb +50 -0
- data/lib/jade/frontend/type_checking/inference/function_call.rb +26 -1
- data/lib/jade/frontend/type_checking/inference/lambda.rb +1 -1
- data/lib/jade/frontend/type_checking/inference/pattern.rb +35 -0
- data/lib/jade/frontend/type_checking/inference/type_alias_declaration.rb +18 -0
- data/lib/jade/frontend/type_checking/inference.rb +2 -0
- data/lib/jade/frontend/type_checking/loader.rb +11 -0
- data/lib/jade/frontend/type_checking/port_resolution.rb +26 -8
- data/lib/jade/frontend/type_checking/substitution.rb +2 -2
- data/lib/jade/frontend/type_checking.rb +3 -1
- data/lib/jade/frontend/usage_analysis.rb +5 -1
- data/lib/jade/interop/boundary.rb +64 -24
- data/lib/jade/interop/error.rb +35 -7
- data/lib/jade/interop/lowering.rb +8 -2
- data/lib/jade/lsp/converters.rb +83 -2
- data/lib/jade/lsp/handlers.rb +122 -25
- data/lib/jade/lsp/snippets.rb +12 -0
- data/lib/jade/lsp/state.rb +11 -4
- data/lib/jade/module_loader/build.rb +36 -0
- data/lib/jade/module_loader.rb +1 -0
- data/lib/jade/parsing/combinators.rb +78 -2
- data/lib/jade/parsing/error.rb +43 -6
- data/lib/jade/parsing/type.rb +5 -3
- data/lib/jade/parsing.rb +140 -26
- data/lib/jade/runtime.rb +32 -13
- data/lib/jade/source.rb +6 -2
- data/lib/jade/stdlib/basics.rb +22 -3
- data/lib/jade/stdlib/char.rb +2 -0
- data/lib/jade/stdlib/clock.rb +6 -2
- data/lib/jade/stdlib/decimal.rb +51 -14
- data/lib/jade/stdlib/intrinsics.rb +59 -6
- data/lib/jade/stdlib/list.rb +37 -1
- data/lib/jade/stdlib/number.rb +47 -0
- data/lib/jade/stdlib/range.rb +211 -0
- data/lib/jade/stdlib.rb +11 -4
- data/lib/jade/symbol/alias.rb +11 -0
- data/lib/jade/symbol/parser.rb +1 -1
- data/lib/jade/symbol.rb +13 -1
- data/lib/jade/task.rb +0 -2
- data/lib/jade/type/anonymous_record.rb +11 -2
- data/lib/jade/type/application.rb +11 -2
- data/lib/jade/type/base.rb +26 -0
- data/lib/jade/type/function.rb +11 -2
- data/lib/jade/type.rb +36 -10
- data/lib/jade/version.rb +1 -1
- metadata +33 -2
|
@@ -5,12 +5,11 @@ module Jade
|
|
|
5
5
|
def self.wildcard(types)
|
|
6
6
|
types
|
|
7
7
|
.map { Wildcard[] }
|
|
8
|
-
.then { [it] }
|
|
9
|
-
.then { Matrix[it, types] }
|
|
8
|
+
.then { Matrix[[it], types] }
|
|
10
9
|
end
|
|
11
10
|
|
|
12
11
|
def self.empty(types = [])
|
|
13
|
-
|
|
12
|
+
Matrix[[], types]
|
|
14
13
|
end
|
|
15
14
|
|
|
16
15
|
def map
|
|
@@ -19,248 +18,58 @@ module Jade
|
|
|
19
18
|
.then { with(rows: it) }
|
|
20
19
|
end
|
|
21
20
|
|
|
22
|
-
def include?(row)
|
|
23
|
-
rows.include?(row)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def size
|
|
27
|
-
rows.size
|
|
28
|
-
end
|
|
29
|
-
|
|
30
21
|
def empty?
|
|
31
22
|
rows.empty?
|
|
32
23
|
end
|
|
33
24
|
|
|
34
|
-
def any?
|
|
35
|
-
rows.any?
|
|
36
|
-
end
|
|
37
|
-
|
|
38
25
|
def concat(other)
|
|
39
26
|
with(rows: rows + other.rows)
|
|
40
27
|
end
|
|
41
28
|
|
|
42
|
-
def
|
|
43
|
-
|
|
44
|
-
return rows.empty? ? Matrix[[[]], types] : Matrix.empty
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
type = types.first
|
|
48
|
-
|
|
49
|
-
return Matrix.empty if never?(type)
|
|
50
|
-
return Matrix.wildcard(types) if rows.empty?
|
|
51
|
-
|
|
52
|
-
heads = head_constructors
|
|
53
|
-
|
|
54
|
-
if infinite?(type) || type_var?(type)
|
|
55
|
-
unmatched_column(env)
|
|
56
|
-
|
|
57
|
-
elsif expandable?(type, env)
|
|
58
|
-
expand(env).missing_patterns(env)
|
|
59
|
-
|
|
60
|
-
elsif heads.empty?
|
|
61
|
-
unmatched_column(env)
|
|
62
|
-
|
|
63
|
-
else
|
|
64
|
-
constructors_of(type, env)
|
|
65
|
-
.reduce(Matrix.empty.with(types:)) do |acc, constructor|
|
|
66
|
-
witnesses(constructor, env, heads.include?(constructor.name))
|
|
67
|
-
.then { acc.concat(it) }
|
|
68
|
-
end
|
|
69
|
-
end
|
|
29
|
+
def heads
|
|
30
|
+
rows.map(&:first)
|
|
70
31
|
end
|
|
71
32
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
# the columns after it.
|
|
77
|
-
def unmatched_column(env)
|
|
78
|
-
default
|
|
79
|
-
.missing_patterns(env)
|
|
80
|
-
.map { [Wildcard[]] + it }
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# A constructor the first column matches is explored by specializing on
|
|
84
|
-
# it. One it never matches can only be missing as a whole, so its
|
|
85
|
-
# witnesses come from the rows that would have covered it — the ones
|
|
86
|
-
# headed by a wildcard. Recursing there instead of into the constructor
|
|
87
|
-
# is what keeps a recursive type from expanding forever.
|
|
88
|
-
def witnesses(constructor, env, matched)
|
|
89
|
-
return Matrix.empty if constructor.args.any? { never?(it) }
|
|
90
|
-
|
|
91
|
-
arity = constructor.args.size
|
|
92
|
-
|
|
93
|
-
if matched
|
|
94
|
-
specialize(constructor)
|
|
95
|
-
.missing_patterns(env)
|
|
96
|
-
.map { [Constructor[constructor.name, it.take(arity)]] + it.drop(arity) }
|
|
97
|
-
|
|
98
|
-
else
|
|
99
|
-
default
|
|
100
|
-
.missing_patterns(env)
|
|
101
|
-
.map { [Constructor[constructor.name, Array.new(arity) { Wildcard[] }]] + it }
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def head_constructors
|
|
106
|
-
rows.filter_map { head_constructor(it.first) }
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def head_constructor(pattern)
|
|
110
|
-
case pattern
|
|
111
|
-
in Constructor(constructor: name) then name
|
|
112
|
-
in Literal(value: true) then 'Basics.True'
|
|
113
|
-
in Literal(value: false) then 'Basics.False'
|
|
114
|
-
in Literal | Record | Wildcard then nil
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def expandable?(type, env)
|
|
119
|
-
case type
|
|
120
|
-
in Type::AnonymousRecord
|
|
121
|
-
true
|
|
122
|
-
|
|
123
|
-
in Type::Application(constructor:)
|
|
124
|
-
env.lookup_def(constructor.name).is_a?(TypeChecking::StructDef)
|
|
125
|
-
|
|
126
|
-
else
|
|
127
|
-
false
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def expand(env)
|
|
132
|
-
type_fields =
|
|
133
|
-
case types.first
|
|
134
|
-
in Type::AnonymousRecord(fields:)
|
|
135
|
-
fields
|
|
136
|
-
|
|
137
|
-
in Type::Application(constructor:)
|
|
138
|
-
env.lookup_def(constructor.name).body.fields
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
map do |row|
|
|
142
|
-
case row.first
|
|
143
|
-
in Record(fields:)
|
|
144
|
-
type_fields
|
|
145
|
-
.map do |(k, v)|
|
|
146
|
-
fields[k] || Wildcard[]
|
|
147
|
-
end + row.drop(1)
|
|
148
|
-
|
|
149
|
-
in Wildcard
|
|
150
|
-
type_fields.map { Wildcard[] } + row.drop(1)
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
.with(types: type_fields.values + types.drop(1))
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def constructors_of(type, env)
|
|
157
|
-
case type
|
|
158
|
-
in Type::Application(constructor: Type::Constructor(name: 'Basics.Bool'))
|
|
159
|
-
[
|
|
160
|
-
TypeChecking::ConstructorDef['Basics.True', 'Basics.Bool', []],
|
|
161
|
-
TypeChecking::ConstructorDef['Basics.False', 'Basics.Bool', []],
|
|
162
|
-
]
|
|
163
|
-
in Type::Application(constructor: Type::Constructor(name: /^Tuple\.Tuple[2-4]$/ => name), args:)
|
|
164
|
-
[TypeChecking::Definition.constructor(name, name, args)]
|
|
165
|
-
|
|
166
|
-
in Type::Application(constructor: Type::Constructor(name: 'List.List'), args: [elem_type])
|
|
167
|
-
[
|
|
168
|
-
TypeChecking::ConstructorDef['List.Nil', 'List.List', []],
|
|
169
|
-
TypeChecking::ConstructorDef['List.Cons', 'List.List', [elem_type, type]],
|
|
170
|
-
]
|
|
171
|
-
|
|
172
|
-
else
|
|
173
|
-
type_def = env.lookup_def(type.constructor.name)
|
|
174
|
-
|
|
175
|
-
# Type the user can't destructure from here (transitively reached
|
|
176
|
-
# through an imported struct field, or opaque intrinsic) —
|
|
177
|
-
# any pattern they can write is trivially exhaustive.
|
|
178
|
-
return [] if type_def.nil? || type_def.opaque?
|
|
179
|
-
|
|
180
|
-
type_def
|
|
181
|
-
.constructors
|
|
182
|
-
.map do |con|
|
|
183
|
-
con
|
|
184
|
-
.args
|
|
185
|
-
.map do |arg|
|
|
186
|
-
if type_var?(arg)
|
|
187
|
-
type_def
|
|
188
|
-
.type_params
|
|
189
|
-
.find_index { arg.name == it.name }
|
|
190
|
-
.then { type.args.at(it) }
|
|
191
|
-
|
|
192
|
-
else
|
|
193
|
-
arg
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
.then { con.with(args: it) }
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def specialize(constructor)
|
|
33
|
+
# Rows the case can match, each opened up into the columns the case
|
|
34
|
+
# carries. A row headed by a wildcard matches every case, and says
|
|
35
|
+
# nothing about the columns it opens.
|
|
36
|
+
def specialize(kase)
|
|
202
37
|
rows
|
|
203
|
-
.filter_map do |
|
|
204
|
-
|
|
205
|
-
in Literal(value:)
|
|
206
|
-
next [] + cols.drop(1) if value == false && "Basics.False" == constructor.name
|
|
207
|
-
next [] + cols.drop(1) if value == true && "Basics.True" == constructor.name
|
|
208
|
-
|
|
209
|
-
in Constructor(constructor: cons_name, args:)
|
|
210
|
-
args + cols.drop(1) if cons_name == constructor.name
|
|
211
|
-
|
|
212
|
-
in Wildcard
|
|
213
|
-
constructor.args.map { Wildcard[] } + cols.drop(1)
|
|
38
|
+
.filter_map do |row|
|
|
39
|
+
head = row.first
|
|
214
40
|
|
|
41
|
+
if kase.matches?(head)
|
|
42
|
+
head.args + row.drop(1)
|
|
43
|
+
elsif head.wildcard?
|
|
44
|
+
Array.new(kase.arity) { Wildcard[] } + row.drop(1)
|
|
215
45
|
end
|
|
216
46
|
end
|
|
217
|
-
.then {
|
|
218
|
-
.then { it.with(types: constructor.args + types.drop(1)) }
|
|
47
|
+
.then { Matrix[it, kase.arg_types + types.drop(1)] }
|
|
219
48
|
end
|
|
220
49
|
|
|
50
|
+
# The rows that survive when the first column is decided by no case at
|
|
51
|
+
# all — only the ones that were not asking about it.
|
|
221
52
|
def default
|
|
222
53
|
rows
|
|
223
54
|
.select { it.first.wildcard? }
|
|
224
55
|
.map { it.drop(1) }
|
|
225
|
-
.then {
|
|
226
|
-
.with(types: types.drop(1))
|
|
56
|
+
.then { Matrix[it, types.drop(1)] }
|
|
227
57
|
end
|
|
228
58
|
|
|
229
|
-
def
|
|
230
|
-
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
private
|
|
234
|
-
|
|
235
|
-
def never?(type)
|
|
236
|
-
case type
|
|
237
|
-
in Type::Application(constructor: Type::Constructor(name: 'Basics.Never'))
|
|
238
|
-
true
|
|
239
|
-
else
|
|
240
|
-
false
|
|
241
|
-
end
|
|
242
|
-
end
|
|
59
|
+
def expand(env)
|
|
60
|
+
fields = Signature.fields(types.first, env)
|
|
243
61
|
|
|
244
|
-
|
|
245
|
-
|
|
62
|
+
map { |row| expand_row(row, fields) }
|
|
63
|
+
.with(types: fields.values + types.drop(1))
|
|
246
64
|
end
|
|
247
65
|
|
|
248
|
-
def
|
|
249
|
-
case
|
|
250
|
-
in
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
in Type::Application(constructor:)
|
|
254
|
-
case constructor
|
|
255
|
-
in Type::Constructor(name: 'Basics.Int') then true
|
|
256
|
-
in Type::Constructor(name: 'Basics.Float') then true
|
|
257
|
-
in Type::Constructor(name: 'String.String') then true
|
|
258
|
-
else
|
|
259
|
-
false
|
|
260
|
-
end
|
|
66
|
+
def expand_row(row, fields)
|
|
67
|
+
case row.first
|
|
68
|
+
in Record(fields: matched)
|
|
69
|
+
fields.keys.map { matched[it] || Wildcard[] } + row.drop(1)
|
|
261
70
|
|
|
262
|
-
|
|
263
|
-
|
|
71
|
+
in Wildcard
|
|
72
|
+
fields.keys.map { Wildcard[] } + row.drop(1)
|
|
264
73
|
end
|
|
265
74
|
end
|
|
266
75
|
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module PatternAnalysis
|
|
4
|
+
Wildcard = Data.define do
|
|
5
|
+
def wildcard?
|
|
6
|
+
true
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def args
|
|
10
|
+
[]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
'_'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Literal = Data.define(:value) do
|
|
19
|
+
def wildcard?
|
|
20
|
+
false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def args
|
|
24
|
+
[]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def to_s
|
|
28
|
+
value.inspect
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Constructor = Data.define(:constructor, :args) do
|
|
33
|
+
def wildcard?
|
|
34
|
+
false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_s
|
|
38
|
+
if constructor.start_with?('Tuple.')
|
|
39
|
+
"(#{args.map(&:to_s).join(', ')})"
|
|
40
|
+
else
|
|
41
|
+
name = constructor.split('.').last
|
|
42
|
+
args.empty? ? name : "#{name}(#{args.map(&:to_s).join(', ')})"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
Record = Data.define(:fields) do
|
|
48
|
+
def wildcard?
|
|
49
|
+
false
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def args
|
|
53
|
+
[]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def to_s
|
|
57
|
+
fields
|
|
58
|
+
.map { |k, v| "#{k}: #{v}" }
|
|
59
|
+
.join(', ')
|
|
60
|
+
.then { "{ #{it} }" }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# An inclusive span of integers. A nil bound is unbounded on that side.
|
|
65
|
+
Interval = Data.define(:from, :to) do
|
|
66
|
+
def wildcard?
|
|
67
|
+
false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def args
|
|
71
|
+
[]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def covers?(other)
|
|
75
|
+
(from.nil? || (other.from && other.from >= from)) &&
|
|
76
|
+
(to.nil? || (other.to && other.to <= to))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def to_s
|
|
80
|
+
case [from, to]
|
|
81
|
+
in [nil, nil] then '_'
|
|
82
|
+
in [n, ^n] then n.to_s
|
|
83
|
+
else "#{from}..#{to}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
module Pattern
|
|
89
|
+
extend self
|
|
90
|
+
|
|
91
|
+
def from_node(node)
|
|
92
|
+
case node
|
|
93
|
+
in AST::Pattern::Record(fields:)
|
|
94
|
+
fields
|
|
95
|
+
.to_h { [it.name, from_node(it.pattern)] }
|
|
96
|
+
.then { Record[it] }
|
|
97
|
+
|
|
98
|
+
in AST::Pattern::Constructor(constructor:, patterns:)
|
|
99
|
+
patterns
|
|
100
|
+
.map { from_node(it) }
|
|
101
|
+
.then { Constructor[constructor.symbol.qualified_name, it] }
|
|
102
|
+
|
|
103
|
+
# `[x, y | xs]` and `[x, y]` are the same Cons/Nil spine the checker
|
|
104
|
+
# already knows how to split.
|
|
105
|
+
in AST::Pattern::List(patterns:, rest:)
|
|
106
|
+
patterns
|
|
107
|
+
.map { from_node(it) }
|
|
108
|
+
.reverse
|
|
109
|
+
.reduce(rest ? Wildcard[] : Constructor['List.Nil', []]) do |acc, head|
|
|
110
|
+
Constructor['List.Cons', [head, acc]]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
in AST::Pattern::Range(from:, to:)
|
|
114
|
+
Interval[from&.value, to&.value]
|
|
115
|
+
|
|
116
|
+
in AST::Pattern::Binding | AST::Pattern::Wildcard
|
|
117
|
+
Wildcard[]
|
|
118
|
+
|
|
119
|
+
in AST::Pattern::Literal(literal: { value: true })
|
|
120
|
+
Constructor['Basics.True', []]
|
|
121
|
+
|
|
122
|
+
in AST::Pattern::Literal(literal: { value: false })
|
|
123
|
+
Constructor['Basics.False', []]
|
|
124
|
+
|
|
125
|
+
in AST::Pattern::Literal(literal: { value: })
|
|
126
|
+
Literal[value]
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module PatternAnalysis
|
|
4
|
+
module Redundancy
|
|
5
|
+
extend self
|
|
6
|
+
|
|
7
|
+
def assert(patterns, env, expected)
|
|
8
|
+
rows = patterns.map { [Pattern.from_node(it)] }
|
|
9
|
+
|
|
10
|
+
rows
|
|
11
|
+
.each_with_index
|
|
12
|
+
.filter_map do |row, index|
|
|
13
|
+
above = Matrix[rows.take(index), [expected]]
|
|
14
|
+
|
|
15
|
+
next if Usefulness.useful?(above, row, env)
|
|
16
|
+
|
|
17
|
+
TypeChecking::Error::UnreachableBranch
|
|
18
|
+
.new(env.entry_name, patterns[index].range)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module PatternAnalysis
|
|
4
|
+
# One shape a column's first pattern can take. `arg_types` are the
|
|
5
|
+
# columns it opens up when a row is specialized on it.
|
|
6
|
+
ConstructorCase = Data.define(:name, :arg_types) do
|
|
7
|
+
def arity
|
|
8
|
+
arg_types.size
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def matches?(pattern)
|
|
12
|
+
pattern in Constructor(constructor: ^(name))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def witness(args)
|
|
16
|
+
Constructor[name, args]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# A literal the rows mention. Types whose values are literals have no
|
|
21
|
+
# enumerable set of these, so one is only ever split on because some row
|
|
22
|
+
# asked for it by name.
|
|
23
|
+
ValueCase = Data.define(:value) do
|
|
24
|
+
def name
|
|
25
|
+
value
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def arity
|
|
29
|
+
0
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def arg_types
|
|
33
|
+
[]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def matches?(pattern)
|
|
37
|
+
pattern in Literal(value: ^(value))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def witness(_args)
|
|
41
|
+
Literal[value]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# A stretch of the integer line no pattern in the column divides any
|
|
46
|
+
# further, so it is either wholly matched or wholly missed.
|
|
47
|
+
IntervalCase = Data.define(:from, :to) do
|
|
48
|
+
def arity
|
|
49
|
+
0
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def arg_types
|
|
53
|
+
[]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def matches?(pattern)
|
|
57
|
+
case pattern
|
|
58
|
+
in Interval then pattern.covers?(self)
|
|
59
|
+
in Literal(value: Integer => n) then n == from && n == to
|
|
60
|
+
else false
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def witness(_args)
|
|
65
|
+
Interval[from, to]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The cases a column of a given type can be split into, and whether they
|
|
70
|
+
# account for every value of that type. When they don't, a row headed by
|
|
71
|
+
# a wildcard covers ground no case names, so the column cannot be
|
|
72
|
+
# decided by splitting alone.
|
|
73
|
+
Split = Data.define(:cases, :total) do
|
|
74
|
+
def covering(head)
|
|
75
|
+
cases.select { it.matches?(head) }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def touched_by?(heads)
|
|
79
|
+
cases.any? { |kase| heads.any? { kase.matches?(it) } }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def covered_by?(heads)
|
|
83
|
+
total && cases.any? &&
|
|
84
|
+
cases.all? { |kase| heads.any? { kase.matches?(it) } }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
module Signature
|
|
89
|
+
extend self
|
|
90
|
+
|
|
91
|
+
UNIT = Split[[], true]
|
|
92
|
+
OPEN = Split[[], false]
|
|
93
|
+
|
|
94
|
+
def of(type, heads, env)
|
|
95
|
+
case type
|
|
96
|
+
in Type::Application(constructor: Type::Constructor(name:), args:)
|
|
97
|
+
named(type, name, args, heads, env)
|
|
98
|
+
|
|
99
|
+
else
|
|
100
|
+
OPEN
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# No value of this type exists, so no row over it can be reached and
|
|
105
|
+
# nothing about it can be missing.
|
|
106
|
+
def uninhabited?(type)
|
|
107
|
+
name_of(type) == 'Basics.Never'
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# A single-shape type is destructured into its fields rather than split
|
|
111
|
+
# into cases, so the columns behind it stay checkable.
|
|
112
|
+
def expandable?(type, env)
|
|
113
|
+
return true if type in Type::AnonymousRecord
|
|
114
|
+
|
|
115
|
+
env.lookup_def(name_of(type)).is_a?(TypeChecking::StructDef)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def fields(type, env)
|
|
119
|
+
case type
|
|
120
|
+
in Type::AnonymousRecord(fields:) then fields
|
|
121
|
+
else env.lookup_def(name_of(type)).body.fields
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def name_of(type)
|
|
128
|
+
case type
|
|
129
|
+
in Type::Application(constructor: Type::Constructor(name:)) then name
|
|
130
|
+
else nil
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def named(type, name, args, heads, env)
|
|
135
|
+
case name
|
|
136
|
+
in 'Basics.Bool'
|
|
137
|
+
Split[
|
|
138
|
+
[ConstructorCase['Basics.True', []], ConstructorCase['Basics.False', []]],
|
|
139
|
+
true,
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
in /^Tuple\.Tuple[2-4]$/
|
|
143
|
+
Split[[ConstructorCase[name, args]], true]
|
|
144
|
+
|
|
145
|
+
in 'List.List'
|
|
146
|
+
args => [element]
|
|
147
|
+
|
|
148
|
+
Split[
|
|
149
|
+
[
|
|
150
|
+
ConstructorCase['List.Nil', []],
|
|
151
|
+
ConstructorCase['List.Cons', [element, type]],
|
|
152
|
+
],
|
|
153
|
+
true,
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
in 'Basics.Int'
|
|
157
|
+
integers(heads)
|
|
158
|
+
|
|
159
|
+
in 'Basics.Float' | 'String.String'
|
|
160
|
+
OPEN
|
|
161
|
+
|
|
162
|
+
else
|
|
163
|
+
union(type, name, env)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Every bound in the column cuts the line in two, and the pieces
|
|
168
|
+
# between consecutive cuts are the coarsest split that still lets each
|
|
169
|
+
# pattern be answered whole. The pieces partition Int, so the result is
|
|
170
|
+
# total even though Int cannot be enumerated: a stretch no pattern
|
|
171
|
+
# names comes back as a gap rather than as an open column.
|
|
172
|
+
def integers(heads)
|
|
173
|
+
cuts = heads.flat_map { cuts_of(it) }.compact.uniq.sort
|
|
174
|
+
|
|
175
|
+
return OPEN if cuts.empty?
|
|
176
|
+
|
|
177
|
+
[nil, *cuts]
|
|
178
|
+
.zip([*cuts.map { it - 1 }, nil])
|
|
179
|
+
.map { |(from, to)| IntervalCase[from, to] }
|
|
180
|
+
.then { Split[it, true] }
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def cuts_of(head)
|
|
184
|
+
case head
|
|
185
|
+
in Interval(from: Integer | nil => from, to: Integer | nil => to)
|
|
186
|
+
[from, to && to + 1]
|
|
187
|
+
in Literal(value: Integer => n) then [n, n + 1]
|
|
188
|
+
else []
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def union(type, name, env)
|
|
193
|
+
type_def = env.lookup_def(name)
|
|
194
|
+
|
|
195
|
+
# A type the user cannot destructure from here — reached through an
|
|
196
|
+
# imported struct's field, or an opaque intrinsic. Any pattern they
|
|
197
|
+
# can write against it is trivially exhaustive.
|
|
198
|
+
return UNIT if type_def.nil? || type_def.opaque?
|
|
199
|
+
return OPEN unless type_def.is_a?(TypeChecking::TypeDef)
|
|
200
|
+
|
|
201
|
+
type_def
|
|
202
|
+
.constructors
|
|
203
|
+
.map { ConstructorCase[it.name, instantiate(it.args, type_def, type)] }
|
|
204
|
+
.then { Split[it, true] }
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def instantiate(args, type_def, type)
|
|
208
|
+
args.map do |arg|
|
|
209
|
+
next arg unless arg.is_a?(Type::Var)
|
|
210
|
+
|
|
211
|
+
type_def
|
|
212
|
+
.type_params
|
|
213
|
+
.find_index { arg.name == it.name }
|
|
214
|
+
.then { type.args.at(it) }
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|