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
|
@@ -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
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'jade/error'
|
|
2
2
|
|
|
3
|
+
require 'jade/frontend/semantic_analysis/error/no_base_case'
|
|
3
4
|
require 'jade/frontend/semantic_analysis/error/circular_extends'
|
|
4
5
|
require 'jade/frontend/semantic_analysis/error/constant_not_callable'
|
|
5
6
|
require 'jade/frontend/semantic_analysis/error/constructor_not_found'
|
|
@@ -9,6 +10,7 @@ require 'jade/frontend/semantic_analysis/error/placeholder_not_allowed'
|
|
|
9
10
|
require 'jade/frontend/semantic_analysis/error/duplicate_function_declaration'
|
|
10
11
|
require 'jade/frontend/semantic_analysis/error/duplicate_implementation'
|
|
11
12
|
require 'jade/frontend/semantic_analysis/error/duplicate_record_field'
|
|
13
|
+
require 'jade/frontend/semantic_analysis/error/implementation_on_alias'
|
|
12
14
|
require 'jade/frontend/semantic_analysis/error/invalid_list_rest_pattern'
|
|
13
15
|
require 'jade/frontend/semantic_analysis/error/kwargs_on_non_constructor'
|
|
14
16
|
require 'jade/frontend/semantic_analysis/error/missing_exposing_clause'
|
|
@@ -21,6 +23,7 @@ require 'jade/frontend/semantic_analysis/error/non_task_port'
|
|
|
21
23
|
require 'jade/frontend/semantic_analysis/error/orphan_implementation'
|
|
22
24
|
require 'jade/frontend/semantic_analysis/error/predicate_must_return_bool'
|
|
23
25
|
require 'jade/frontend/semantic_analysis/error/predicate_name_not_allowed'
|
|
26
|
+
require 'jade/frontend/semantic_analysis/error/recursive_type_alias'
|
|
24
27
|
require 'jade/frontend/semantic_analysis/error/shadowing_error'
|
|
25
28
|
require 'jade/frontend/semantic_analysis/error/type_args_mismatch'
|
|
26
29
|
require 'jade/frontend/semantic_analysis/error/type_param_required'
|
|
@@ -49,6 +49,14 @@ module Jade
|
|
|
49
49
|
Result[results.map(&:node), results.flat_map(&:errors), scope]
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
# A local alias is a `Symbol::Alias`; one that crossed a module arrives
|
|
53
|
+
# as a `TypeRef`, so both spellings have to be resolved before asking.
|
|
54
|
+
def resolved_alias(symbol, registry)
|
|
55
|
+
symbol
|
|
56
|
+
.then { it.is_a?(Symbol::TypeRef) ? registry.lookup(it) : it }
|
|
57
|
+
.then { it if it.is_a?(Symbol::Alias) }
|
|
58
|
+
end
|
|
59
|
+
|
|
52
60
|
private def lookup_applied_type(applied_type, entry)
|
|
53
61
|
case applied_type.constructor
|
|
54
62
|
in AST::TypeName(type:)
|
|
@@ -97,9 +105,28 @@ module Jade
|
|
|
97
105
|
|
|
98
106
|
in Symbol::RecordType(row_var:)
|
|
99
107
|
row_var.nil? ? [] : [row_var]
|
|
108
|
+
|
|
109
|
+
in Symbol::Alias(body:)
|
|
110
|
+
# Aliases are transparent — vars are whatever the body has.
|
|
111
|
+
# Skip if body unresolved (cycle / pre-deep-decl).
|
|
112
|
+
body ? collect_vars(body, registry) : []
|
|
100
113
|
end
|
|
101
114
|
end
|
|
102
115
|
|
|
116
|
+
def validate_inhabited(symbol_ref, registry, entry, kind:)
|
|
117
|
+
symbol = registry.lookup(symbol_ref)
|
|
118
|
+
return [] if Inhabitedness.inhabited?(symbol, registry)
|
|
119
|
+
|
|
120
|
+
[
|
|
121
|
+
Error::NoBaseCase.new(
|
|
122
|
+
entry.name,
|
|
123
|
+
symbol.decl_span,
|
|
124
|
+
name: symbol_ref.name,
|
|
125
|
+
kind:,
|
|
126
|
+
),
|
|
127
|
+
]
|
|
128
|
+
end
|
|
129
|
+
|
|
103
130
|
def validate_type_symbol(symbol, registry, entry)
|
|
104
131
|
case symbol
|
|
105
132
|
in Symbol::Union(variants:, type_params:)
|
|
@@ -154,6 +181,10 @@ module Jade
|
|
|
154
181
|
in Symbol::Struct(type_params:, record_type:)
|
|
155
182
|
validate_type_symbol(record_type, registry, entry) +
|
|
156
183
|
type_params.flat_map { validate_type_symbol(it, registry, entry) }
|
|
184
|
+
|
|
185
|
+
in Symbol::Alias(type_params:, body:)
|
|
186
|
+
(body ? validate_type_symbol(body, registry, entry) : []) +
|
|
187
|
+
type_params.flat_map { validate_type_symbol(it, registry, entry) }
|
|
157
188
|
end
|
|
158
189
|
end
|
|
159
190
|
end
|
|
@@ -20,6 +20,19 @@ module Jade
|
|
|
20
20
|
functions_r = analyze_in_parallel(functions, registry, scope, entry)
|
|
21
21
|
|
|
22
22
|
make_error = ->(klass, **kw) { klass.new(entry.name, node.range, **kw) }
|
|
23
|
+
|
|
24
|
+
resolved_alias(type_sym, registry).then do |alias_sym|
|
|
25
|
+
if alias_sym
|
|
26
|
+
return make_error
|
|
27
|
+
.(
|
|
28
|
+
Error::ImplementationOnAlias,
|
|
29
|
+
interface: entry.lookup_type(interface).qname,
|
|
30
|
+
alias_name: alias_sym.qname,
|
|
31
|
+
)
|
|
32
|
+
.then { Result[node, [it], scope] }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
23
36
|
type_is_local = entry.defined_types.key?(local_type_name(applied_type))
|
|
24
37
|
|
|
25
38
|
unless entry.defined_types.key?(interface) || type_is_local
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module SemanticAnalysis
|
|
4
|
+
# Least fixed point over "can a value of this type be built at all".
|
|
5
|
+
# A type currently being asked about counts as uninhabited, so a shape
|
|
6
|
+
# that needs itself to exist bottoms out at false rather than looping.
|
|
7
|
+
module Inhabitedness
|
|
8
|
+
extend self
|
|
9
|
+
|
|
10
|
+
DECLARATIONS = [Symbol::Union, Symbol::Struct, Symbol::Alias].freeze
|
|
11
|
+
|
|
12
|
+
def inhabited?(symbol, registry)
|
|
13
|
+
reachable?(symbol, registry, ::Set[], {})
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def reachable?(sym, registry, visiting, subst)
|
|
19
|
+
case sym
|
|
20
|
+
in Symbol::Variable(name:)
|
|
21
|
+
subst.fetch(name, true)
|
|
22
|
+
|
|
23
|
+
in Symbol::RecordType(fields:)
|
|
24
|
+
fields.values.all? { reachable?(it, registry, visiting, subst) }
|
|
25
|
+
|
|
26
|
+
in Symbol::TypeRef
|
|
27
|
+
declaration(registry.lookup(sym), [], registry, visiting)
|
|
28
|
+
|
|
29
|
+
in Symbol::TypeApplication(constructor:, args:)
|
|
30
|
+
args
|
|
31
|
+
.map { reachable?(it, registry, visiting, subst) }
|
|
32
|
+
.then { declaration(resolve(constructor, registry), it, registry, visiting) }
|
|
33
|
+
|
|
34
|
+
in Symbol::Union | Symbol::Struct | Symbol::Alias
|
|
35
|
+
declaration(sym, [], registry, visiting)
|
|
36
|
+
|
|
37
|
+
else
|
|
38
|
+
true
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def resolve(constructor, registry)
|
|
43
|
+
constructor.is_a?(Symbol::TypeRef) ? registry.lookup(constructor) : constructor
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def declaration(head, arg_values, registry, visiting)
|
|
47
|
+
return true unless DECLARATIONS.any? { head.is_a?(it) }
|
|
48
|
+
|
|
49
|
+
# A declaration asked about on its own has no arguments to speak of,
|
|
50
|
+
# and a parameter stands for whatever a caller supplies, so it
|
|
51
|
+
# counts as something that exists.
|
|
52
|
+
values = head.type_params.each_index.map { arg_values.fetch(it, true) }
|
|
53
|
+
key = [head.qualified_name, values]
|
|
54
|
+
return false if visiting.include?(key)
|
|
55
|
+
|
|
56
|
+
seen = visiting + [key]
|
|
57
|
+
subst = head.type_params.map(&:name).zip(values).to_h
|
|
58
|
+
|
|
59
|
+
case head
|
|
60
|
+
in Symbol::Alias(body:)
|
|
61
|
+
body.nil? || reachable?(body, registry, seen, subst)
|
|
62
|
+
|
|
63
|
+
in Symbol::Struct(record_type:)
|
|
64
|
+
record_type.nil? || reachable?(record_type, registry, seen, subst)
|
|
65
|
+
|
|
66
|
+
in Symbol::Union(variants:) if tuple?(head)
|
|
67
|
+
values.all?
|
|
68
|
+
|
|
69
|
+
in Symbol::Union(variants:)
|
|
70
|
+
variants.empty? || variants.any? { buildable?(it, registry, seen, subst) }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# A tuple carries no variants to read, and unlike the other natives
|
|
75
|
+
# written that way it is a product: there is no empty one to fall back
|
|
76
|
+
# on the way a `List` has `[]`.
|
|
77
|
+
def tuple?(head)
|
|
78
|
+
head.qualified_name.start_with?('Tuple.Tuple')
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def buildable?(variant_ref, registry, visiting, subst)
|
|
82
|
+
registry
|
|
83
|
+
.lookup(variant_ref)
|
|
84
|
+
.args
|
|
85
|
+
.all? { reachable?(it, registry, visiting, subst) }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -18,7 +18,7 @@ module Jade
|
|
|
18
18
|
.flat_map { validate_type_symbol(it.symbol, registry, entry) }
|
|
19
19
|
|
|
20
20
|
task_errors = functions_with_symbols
|
|
21
|
-
.flat_map { |fn| task_shape_errors(fn, entry) }
|
|
21
|
+
.flat_map { |fn| task_shape_errors(fn, registry, entry) }
|
|
22
22
|
|
|
23
23
|
Result
|
|
24
24
|
.init(node.with(functions: functions_with_symbols), scope)
|
|
@@ -27,38 +27,50 @@ module Jade
|
|
|
27
27
|
|
|
28
28
|
private
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
TASK = 'Task.Task'
|
|
31
|
+
|
|
32
|
+
# Asked of the expanded type rather than the written symbol: an alias
|
|
33
|
+
# is its body, and both questions here are about the shape.
|
|
34
|
+
def task_shape_errors(fn, registry, entry)
|
|
35
|
+
return_type = expand(fn.symbol.return_type, registry)
|
|
36
|
+
|
|
37
|
+
unless task_type?(return_type)
|
|
32
38
|
Error::NonTaskPort
|
|
33
39
|
.new(entry.name, fn.range, fn_name: fn.name)
|
|
34
40
|
.then { return [it] }
|
|
35
41
|
end
|
|
36
42
|
|
|
37
|
-
|
|
43
|
+
return_type => Type::Application(args: [ok_arm, err_arm])
|
|
38
44
|
|
|
39
45
|
[ok_arm, err_arm]
|
|
40
46
|
.any? { contains_task?(it) }
|
|
41
47
|
.then { it ? [Error::NestedTaskPort.new(entry.name, fn.range, fn_name: fn.name)] : [] }
|
|
42
48
|
end
|
|
43
49
|
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
def expand(symbol, registry)
|
|
51
|
+
Type
|
|
52
|
+
.from_symbol(symbol, registry, TypeChecking::VarGen.new)
|
|
53
|
+
.first
|
|
54
|
+
end
|
|
48
55
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
end
|
|
56
|
+
def task_type?(type)
|
|
57
|
+
type in Type::Application(constructor: Type::Constructor(name: TASK))
|
|
52
58
|
end
|
|
53
59
|
|
|
54
|
-
def contains_task?(
|
|
55
|
-
case
|
|
56
|
-
in
|
|
57
|
-
|
|
60
|
+
def contains_task?(type)
|
|
61
|
+
case type
|
|
62
|
+
in Type::Application(constructor: Type::Constructor(name: TASK))
|
|
63
|
+
true
|
|
58
64
|
|
|
59
|
-
in
|
|
65
|
+
in Type::Application(args:)
|
|
60
66
|
args.any? { contains_task?(it) }
|
|
61
67
|
|
|
68
|
+
in Type::Function(args:, return_type:)
|
|
69
|
+
(args + [return_type]).any? { contains_task?(it) }
|
|
70
|
+
|
|
71
|
+
in Type::AnonymousRecord(fields:)
|
|
72
|
+
fields.values.any? { contains_task?(it) }
|
|
73
|
+
|
|
62
74
|
else
|
|
63
75
|
false
|
|
64
76
|
end
|
|
@@ -44,8 +44,10 @@ module Jade
|
|
|
44
44
|
# still bare AST and `Placeholder.lift` can turn the call into a
|
|
45
45
|
# lambda — the same route a positional partial application takes.
|
|
46
46
|
def partially_applied(node, callee, fields, parent, constructor, registry, scope, entry, callee_r)
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
# A positional variant has no field names, so every key is also
|
|
48
|
+
# an unknown field. The placeholder error is the one that says why.
|
|
49
|
+
errors = placeholder_errors(fields, parent, constructor, entry)
|
|
50
|
+
.then { it.any? ? it : Validation.errors(node, fields, parent, constructor, registry, entry) }
|
|
49
51
|
|
|
50
52
|
return Result[node, callee_r.errors + errors, scope] if errors.any?
|
|
51
53
|
|
|
@@ -81,12 +83,26 @@ module Jade
|
|
|
81
83
|
end
|
|
82
84
|
end
|
|
83
85
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
# Validation already reports a keyed call on a plain function. A
|
|
87
|
+
# keyed variant lowers to a record literal, which takes holes.
|
|
88
|
+
def placeholder_errors(fields, parent, constructor, entry)
|
|
89
|
+
return [] if constructor.nil? || parent.is_a?(Symbol::Struct)
|
|
90
|
+
return [] if keyed_variant?(constructor)
|
|
86
91
|
|
|
87
92
|
fields
|
|
88
93
|
.select { it.value.is_a?(AST::Placeholder) }
|
|
89
|
-
.map
|
|
94
|
+
.map do
|
|
95
|
+
Error::PlaceholderNotAllowed.new(
|
|
96
|
+
entry.name,
|
|
97
|
+
it.range,
|
|
98
|
+
field: it.key,
|
|
99
|
+
name: constructor.name,
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def keyed_variant?(constructor)
|
|
105
|
+
constructor in Symbol::Constructor(args: [Symbol::RecordType])
|
|
90
106
|
end
|
|
91
107
|
|
|
92
108
|
def lower(node, callee, fields, parent, constructor, registry)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module SemanticAnalysis
|
|
4
|
+
module PatternRange
|
|
5
|
+
extend self
|
|
6
|
+
extend Helper
|
|
7
|
+
|
|
8
|
+
# Both bounds are integer literals, so there is no name to resolve and
|
|
9
|
+
# nothing to bind.
|
|
10
|
+
def analyze(node, _registry, scope, _entry)
|
|
11
|
+
Result.init(node, scope)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -14,6 +14,7 @@ module Jade
|
|
|
14
14
|
.init(node.with(symbol: symbol_ref), scope)
|
|
15
15
|
.add_errors(validate_no_unbound_vars(symbol_ref, registry, entry))
|
|
16
16
|
.add_errors(validate_type_symbol(symbol_ref, registry, entry))
|
|
17
|
+
.add_errors(validate_inhabited(symbol_ref, registry, entry, kind: 'struct'))
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
private
|