jade-lang 0.9.0 → 0.10.1
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 +442 -12
- data/README.md +1 -0
- data/docs/interop.md +18 -1
- data/docs/json.md +34 -3
- 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/init.rb +86 -0
- data/lib/jade/cli.rb +4 -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 +38 -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/formatter/type.rb +3 -5
- 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/record_update_type_mismatch.rb +21 -0
- 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 +7 -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/record_update.rb +8 -1
- 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 +6 -4
- 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/decode.rb +11 -0
- 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/stdlib_function.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 -3
- data/lib/jade/type.rb +36 -10
- data/lib/jade/version.rb +1 -1
- metadata +35 -2
|
@@ -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
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module PatternAnalysis
|
|
4
|
+
# Maranget's other half: can any value reach `row` that the rows above it
|
|
5
|
+
# did not already take? A branch that is not useful is dead code.
|
|
6
|
+
module Usefulness
|
|
7
|
+
extend self
|
|
8
|
+
|
|
9
|
+
def useful?(matrix, row, env)
|
|
10
|
+
return matrix.empty? if matrix.types.empty?
|
|
11
|
+
|
|
12
|
+
type = matrix.types.first
|
|
13
|
+
|
|
14
|
+
return false if Signature.uninhabited?(type)
|
|
15
|
+
return expanded(matrix, row, env) if Signature.expandable?(type, env)
|
|
16
|
+
|
|
17
|
+
# The row under test cuts the column too, so its own bounds have to
|
|
18
|
+
# be in the split or it will not line up with the cases.
|
|
19
|
+
split = Signature.of(type, matrix.heads + [row.first], env)
|
|
20
|
+
|
|
21
|
+
case row.first
|
|
22
|
+
in Wildcard
|
|
23
|
+
open_column(matrix, row, split, env)
|
|
24
|
+
|
|
25
|
+
in Interval
|
|
26
|
+
split.covering(row.first).any? { narrowed(matrix, row, it, env) }
|
|
27
|
+
|
|
28
|
+
in Literal(value:)
|
|
29
|
+
split
|
|
30
|
+
.covering(row.first)
|
|
31
|
+
.then { it.empty? ? [ValueCase[value]] : it }
|
|
32
|
+
.any? { narrowed(matrix, row, it, env) }
|
|
33
|
+
|
|
34
|
+
# Deadness is a proof. A constructor this column's type does not have
|
|
35
|
+
# yields none, so the branch stands.
|
|
36
|
+
in Constructor
|
|
37
|
+
split
|
|
38
|
+
.covering(row.first)
|
|
39
|
+
.then { it.empty? || it.any? { narrowed(matrix, row, it, env) } }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# A wildcard reaches whatever the rows above left open. When they name
|
|
46
|
+
# every case of the type there is no such ground, and the question
|
|
47
|
+
# moves inside the cases instead.
|
|
48
|
+
def open_column(matrix, row, split, env)
|
|
49
|
+
if split.covered_by?(matrix.heads)
|
|
50
|
+
split.cases.any? { narrowed(matrix, row, it, env) }
|
|
51
|
+
else
|
|
52
|
+
useful?(matrix.default, row.drop(1), env)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def narrowed(matrix, row, kase, env)
|
|
57
|
+
head = row.first
|
|
58
|
+
args =
|
|
59
|
+
kase.matches?(head) ? head.args : Array.new(kase.arity) { Wildcard[] }
|
|
60
|
+
|
|
61
|
+
useful?(matrix.specialize(kase), args + row.drop(1), env)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def expanded(matrix, row, env)
|
|
65
|
+
fields = Signature.fields(matrix.types.first, env)
|
|
66
|
+
|
|
67
|
+
useful?(matrix.expand(env), matrix.expand_row(row, fields), env)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module PatternAnalysis
|
|
4
|
+
# Maranget's witness generation: the patterns a match would need to add
|
|
5
|
+
# to become exhaustive. An empty result means it already is.
|
|
6
|
+
module Witnesses
|
|
7
|
+
extend self
|
|
8
|
+
|
|
9
|
+
def missing(matrix, env)
|
|
10
|
+
return exhausted_columns(matrix) if matrix.types.empty?
|
|
11
|
+
|
|
12
|
+
type = matrix.types.first
|
|
13
|
+
|
|
14
|
+
return Matrix.empty if Signature.uninhabited?(type)
|
|
15
|
+
return Matrix.wildcard(matrix.types) if matrix.empty?
|
|
16
|
+
return expanded(matrix, env) if Signature.expandable?(type, env)
|
|
17
|
+
|
|
18
|
+
heads = matrix.heads
|
|
19
|
+
split = Signature.of(type, heads, env)
|
|
20
|
+
|
|
21
|
+
return unmatched_column(matrix, env) unless split.touched_by?(heads)
|
|
22
|
+
|
|
23
|
+
split
|
|
24
|
+
.cases
|
|
25
|
+
.reduce(Matrix.empty(matrix.types)) do |acc, kase|
|
|
26
|
+
witnesses(matrix, kase, env, heads.any? { kase.matches?(it) })
|
|
27
|
+
.then { acc.concat(it) }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def expanded(matrix, env)
|
|
34
|
+
missing(matrix.expand(env), env)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def exhausted_columns(matrix)
|
|
38
|
+
matrix.empty? ? Matrix[[[]], []] : Matrix.empty
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Nothing in this column narrows anything — a type with no cases to
|
|
42
|
+
# enumerate, or one every row left open. Whatever is missing lives in
|
|
43
|
+
# the columns after it.
|
|
44
|
+
def unmatched_column(matrix, env)
|
|
45
|
+
missing(matrix.default, env)
|
|
46
|
+
.map { [Wildcard[]] + it }
|
|
47
|
+
.with(types: matrix.types)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# A case the first column matches is explored by specializing on it.
|
|
51
|
+
# One it never matches can only be missing as a whole, so its witnesses
|
|
52
|
+
# come from the rows that would have covered it — the ones headed by a
|
|
53
|
+
# wildcard. Recursing there instead of into the case is what keeps a
|
|
54
|
+
# recursive type from expanding forever.
|
|
55
|
+
def witnesses(matrix, kase, env, matched)
|
|
56
|
+
return Matrix.empty if kase.arg_types.any? { Signature.uninhabited?(it) }
|
|
57
|
+
|
|
58
|
+
found =
|
|
59
|
+
if matched
|
|
60
|
+
missing(matrix.specialize(kase), env)
|
|
61
|
+
.map { [kase.witness(it.take(kase.arity))] + it.drop(kase.arity) }
|
|
62
|
+
|
|
63
|
+
else
|
|
64
|
+
missing(matrix.default, env)
|
|
65
|
+
.map { [kase.witness(Array.new(kase.arity) { Wildcard[] })] + it }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
found.with(types: matrix.types)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -1,40 +1,7 @@
|
|
|
1
|
+
require 'jade/frontend/pattern_analysis/pattern'
|
|
2
|
+
require 'jade/frontend/pattern_analysis/signature'
|
|
1
3
|
require 'jade/frontend/pattern_analysis/matrix'
|
|
4
|
+
require 'jade/frontend/pattern_analysis/witnesses'
|
|
5
|
+
require 'jade/frontend/pattern_analysis/usefulness'
|
|
2
6
|
require 'jade/frontend/pattern_analysis/exhaustiveness'
|
|
3
|
-
|
|
4
|
-
module Jade
|
|
5
|
-
module Frontend
|
|
6
|
-
module PatternAnalysis
|
|
7
|
-
Wildcard = Data.define do
|
|
8
|
-
def wildcard?; true; end
|
|
9
|
-
def to_s; '_'; end
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
Literal = Data.define(:value, :type) do
|
|
13
|
-
def wildcard?; false; end
|
|
14
|
-
def to_s; value.inspect; end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
Constructor = Data.define(:constructor, :args) do
|
|
18
|
-
def wildcard?; false; end
|
|
19
|
-
|
|
20
|
-
def to_s
|
|
21
|
-
if constructor.start_with?('Tuple.')
|
|
22
|
-
"(#{args.map(&:to_s).join(', ')})"
|
|
23
|
-
else
|
|
24
|
-
name = constructor.split('.').last
|
|
25
|
-
args.empty? ? name : "#{name}(#{args.map(&:to_s).join(', ')})"
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
Record = Data.define(:fields) do
|
|
31
|
-
def wildcard?; false; end
|
|
32
|
-
|
|
33
|
-
def to_s
|
|
34
|
-
fields_str = fields.map { |k, v| "#{k}: #{v}" }.join(', ')
|
|
35
|
-
"{ #{fields_str} }"
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
7
|
+
require 'jade/frontend/pattern_analysis/redundancy'
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module SemanticAnalysis
|
|
4
|
+
module Error
|
|
5
|
+
class ImplementationOnAlias < Jade::Error
|
|
6
|
+
def initialize(entry, span, interface:, alias_name:)
|
|
7
|
+
super(entry:, span:)
|
|
8
|
+
@interface = interface
|
|
9
|
+
@alias_name = alias_name
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def message
|
|
13
|
+
"Cannot implement `#{@interface}` for type alias `#{@alias_name}`; " \
|
|
14
|
+
"an alias is its body, so use a `struct` or a single-variant `type`"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def label
|
|
18
|
+
"no impls on type aliases"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module SemanticAnalysis
|
|
4
|
+
module Error
|
|
5
|
+
class NoBaseCase < Jade::Error
|
|
6
|
+
HELP = {
|
|
7
|
+
'struct' => 'wrap the field in something that has an empty case: ' \
|
|
8
|
+
'`Maybe(%<name>s)` can be `Nothing`, `List(%<name>s)` can be empty',
|
|
9
|
+
'type' => 'add a variant that does not mention `%<name>s`, ' \
|
|
10
|
+
'the way `Nothing` does for `Maybe`',
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
def initialize(entry, span, name:, kind:)
|
|
14
|
+
@name = name
|
|
15
|
+
@kind = kind
|
|
16
|
+
super(entry:, span:)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def message
|
|
20
|
+
"`#{@name}` can never be built: every `#{@name}` needs a `#{@name}` " \
|
|
21
|
+
'inside it first'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def label
|
|
25
|
+
'nothing here stops the recursion'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def notes
|
|
29
|
+
[
|
|
30
|
+
Jade::Diagnostics::Annotation[
|
|
31
|
+
:note,
|
|
32
|
+
"a type may refer to itself; `#{@name}` just has no case that " \
|
|
33
|
+
"stops, so there is no smallest `#{@name}` to start from",
|
|
34
|
+
],
|
|
35
|
+
Jade::Diagnostics::Annotation[
|
|
36
|
+
:help,
|
|
37
|
+
format(HELP.fetch(@kind), name: @name),
|
|
38
|
+
],
|
|
39
|
+
]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -2,18 +2,30 @@ module Jade
|
|
|
2
2
|
module Frontend
|
|
3
3
|
module SemanticAnalysis
|
|
4
4
|
module Error
|
|
5
|
+
# `Rect(w: _, h: 2)` where `Rect` is `Rect(Int, Int)`: positional, so
|
|
6
|
+
# `w:` names nothing. A variant carrying a record takes the hole.
|
|
5
7
|
class PlaceholderNotAllowed < Jade::Error
|
|
6
|
-
def initialize(entry, span, field:)
|
|
8
|
+
def initialize(entry, span, field:, name:)
|
|
7
9
|
@field = field
|
|
10
|
+
@name = name
|
|
8
11
|
super(entry:, span:)
|
|
9
12
|
end
|
|
10
13
|
|
|
11
14
|
def message
|
|
12
|
-
"`#{@
|
|
15
|
+
"`#{@name}` is a union variant; its arguments are positional " \
|
|
16
|
+
"and have no names, so `#{@field}:` has nothing to refer to"
|
|
13
17
|
end
|
|
14
18
|
|
|
15
19
|
def label
|
|
16
|
-
|
|
20
|
+
'not allowed here'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def notes
|
|
24
|
+
[Jade::Diagnostics::Annotation[
|
|
25
|
+
:help,
|
|
26
|
+
"write `#{@name}(_, x)` positionally, or pipe into the " \
|
|
27
|
+
"first argument with `|> #{@name}(x)`",
|
|
28
|
+
]]
|
|
17
29
|
end
|
|
18
30
|
end
|
|
19
31
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module SemanticAnalysis
|
|
4
|
+
module Error
|
|
5
|
+
class RecursiveTypeAlias < Jade::Error
|
|
6
|
+
def initialize(entry, span, name:, cycle:)
|
|
7
|
+
super(entry:, span:)
|
|
8
|
+
@name = name
|
|
9
|
+
@cycle = cycle
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def message
|
|
13
|
+
"Type alias `#{@name}` is recursive (cycle: #{@cycle.join(' -> ')}); " \
|
|
14
|
+
"an alias must be finite, so use a `type` for a recursive shape"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def label
|
|
18
|
+
"recursive type alias"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|