jade-lang 0.8.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 +439 -2
- 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/error.rb +5 -0
- data/lib/jade/extensions.rb +70 -0
- 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/body.rb +29 -1
- data/lib/jade/frontend/semantic_analysis/error/duplicate_implementation.rb +39 -0
- 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 +4 -0
- data/lib/jade/frontend/semantic_analysis/helper.rb +41 -0
- data/lib/jade/frontend/semantic_analysis/implementation.rb +13 -10
- 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/deriving.rb +5 -4
- 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 +47 -2
- 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 +74 -56
- 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
- data/lib/jade.rb +1 -0
- metadata +35 -3
- data/lib/jade/frontend/type_checking/constraints/deriving/assignable.rb +0 -170
|
@@ -49,6 +49,24 @@ 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
|
+
|
|
60
|
+
private def lookup_applied_type(applied_type, entry)
|
|
61
|
+
case applied_type.constructor
|
|
62
|
+
in AST::TypeName(type:)
|
|
63
|
+
entry.lookup_type(type)
|
|
64
|
+
in AST::QualifiedTypeName(path:)
|
|
65
|
+
*module_parts, type_name = path
|
|
66
|
+
entry.lookup_qualified_type(module_parts.join('.'), type_name)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
52
70
|
def analyze_duplicate_fields(fields, entry)
|
|
53
71
|
fields
|
|
54
72
|
.group_by(&:key)
|
|
@@ -87,9 +105,28 @@ module Jade
|
|
|
87
105
|
|
|
88
106
|
in Symbol::RecordType(row_var:)
|
|
89
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) : []
|
|
90
113
|
end
|
|
91
114
|
end
|
|
92
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
|
+
|
|
93
130
|
def validate_type_symbol(symbol, registry, entry)
|
|
94
131
|
case symbol
|
|
95
132
|
in Symbol::Union(variants:, type_params:)
|
|
@@ -144,6 +181,10 @@ module Jade
|
|
|
144
181
|
in Symbol::Struct(type_params:, record_type:)
|
|
145
182
|
validate_type_symbol(record_type, registry, entry) +
|
|
146
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) }
|
|
147
188
|
end
|
|
148
189
|
end
|
|
149
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
|
|
@@ -73,16 +86,6 @@ module Jade
|
|
|
73
86
|
|
|
74
87
|
private
|
|
75
88
|
|
|
76
|
-
def lookup_applied_type(applied_type, entry)
|
|
77
|
-
case applied_type.constructor
|
|
78
|
-
in AST::TypeName(type:)
|
|
79
|
-
entry.lookup_type(type)
|
|
80
|
-
in AST::QualifiedTypeName(path:)
|
|
81
|
-
*module_parts, type_name = path
|
|
82
|
-
entry.lookup_qualified_type(module_parts.join('.'), type_name)
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
89
|
def local_type_name(applied_type)
|
|
87
90
|
case applied_type.constructor
|
|
88
91
|
in AST::TypeName(type:) then type
|
|
@@ -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
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module SemanticAnalysis
|
|
4
|
+
module TypeAliasDeclaration
|
|
5
|
+
# An alias is replaced by its body, so one that reaches itself would
|
|
6
|
+
# expand forever. This finds the path back, or nothing.
|
|
7
|
+
module CycleDetection
|
|
8
|
+
extend self
|
|
9
|
+
|
|
10
|
+
def call(symbol_ref, registry, entry)
|
|
11
|
+
symbol = registry.lookup(symbol_ref)
|
|
12
|
+
return [] unless symbol.body
|
|
13
|
+
|
|
14
|
+
qname = symbol.qualified_name
|
|
15
|
+
cycle = find(qname, symbol.body, registry, [qname], ::Set[])
|
|
16
|
+
|
|
17
|
+
return [] unless cycle
|
|
18
|
+
return [] unless reports_for?(qname, cycle, registry)
|
|
19
|
+
|
|
20
|
+
[
|
|
21
|
+
Error::RecursiveTypeAlias.new(
|
|
22
|
+
entry.name,
|
|
23
|
+
symbol.decl_span,
|
|
24
|
+
name: symbol_ref.name,
|
|
25
|
+
cycle: cycle.map { Symbol.unqualified_name(it) },
|
|
26
|
+
),
|
|
27
|
+
]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# Every alias in a cycle finds it, so `A = B, B = A` would be
|
|
33
|
+
# reported twice for one mistake. The first by declaration reports
|
|
34
|
+
# and the rest stay quiet, which needs no state shared between
|
|
35
|
+
# declarations since each walk already knows the whole cycle.
|
|
36
|
+
def reports_for?(qname, cycle, registry)
|
|
37
|
+
qname == cycle.min_by { declaration_order(it, registry) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def declaration_order(qname, registry)
|
|
41
|
+
Symbol
|
|
42
|
+
.type_ref_from_qualified_name(qname)
|
|
43
|
+
.then { registry.lookup(it) }
|
|
44
|
+
.then { [it.decl_span&.begin || 0, qname] }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns the qname-path back to `start_qname` if found, nil
|
|
48
|
+
# otherwise. Whether an alias reaches `start_qname` does not depend
|
|
49
|
+
# on the path taken to it, so `explored` is shared across the whole
|
|
50
|
+
# walk and an alias that came up clean once is never walked again.
|
|
51
|
+
# Without it an alias naming another one twice doubles the work per
|
|
52
|
+
# level.
|
|
53
|
+
def find(start_qname, sym, registry, visited, explored)
|
|
54
|
+
case sym
|
|
55
|
+
in Symbol::TypeRef
|
|
56
|
+
resolved = registry.lookup(sym)
|
|
57
|
+
return nil unless resolved.is_a?(Symbol::Alias)
|
|
58
|
+
|
|
59
|
+
qname = resolved.qualified_name
|
|
60
|
+
return visited + [qname] if qname == start_qname
|
|
61
|
+
return nil unless explored.add?(qname)
|
|
62
|
+
|
|
63
|
+
resolved.body &&
|
|
64
|
+
find(start_qname, resolved.body, registry, visited + [qname], explored)
|
|
65
|
+
|
|
66
|
+
in Symbol::TypeApplication(constructor:, args:)
|
|
67
|
+
first(start_qname, [constructor, *args], registry, visited, explored)
|
|
68
|
+
|
|
69
|
+
in Symbol::PartialApplication(constructor:, args:)
|
|
70
|
+
first(start_qname, [constructor, *args], registry, visited, explored)
|
|
71
|
+
|
|
72
|
+
in Symbol::FunctionType(params:, return_type:)
|
|
73
|
+
first(start_qname, params + [return_type], registry, visited, explored)
|
|
74
|
+
|
|
75
|
+
in Symbol::RecordType(fields:)
|
|
76
|
+
first(start_qname, fields.values, registry, visited, explored)
|
|
77
|
+
|
|
78
|
+
# Leaves (Variable, primitives, etc.) — can't transitively
|
|
79
|
+
# contain an alias reference, so no cycle through them.
|
|
80
|
+
else
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def first(start_qname, syms, registry, visited, explored)
|
|
86
|
+
syms
|
|
87
|
+
.lazy
|
|
88
|
+
.filter_map { find(start_qname, it, registry, visited, explored) }
|
|
89
|
+
.first
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'jade/frontend/semantic_analysis/type_alias_declaration/cycle_detection'
|
|
2
|
+
|
|
3
|
+
module Jade
|
|
4
|
+
module Frontend
|
|
5
|
+
module SemanticAnalysis
|
|
6
|
+
module TypeAliasDeclaration
|
|
7
|
+
extend self
|
|
8
|
+
extend Helper
|
|
9
|
+
|
|
10
|
+
def analyze(node, registry, scope, entry)
|
|
11
|
+
node => AST::TypeAliasDeclaration(name:)
|
|
12
|
+
|
|
13
|
+
symbol_ref = entry.lookup_type(name).to_ref
|
|
14
|
+
|
|
15
|
+
Result
|
|
16
|
+
.init(node.with(symbol: symbol_ref), scope)
|
|
17
|
+
.add_errors(validate_no_unbound_vars(symbol_ref, registry, entry))
|
|
18
|
+
.add_errors(validate_type_symbol(symbol_ref, registry, entry))
|
|
19
|
+
.add_errors(CycleDetection.call(symbol_ref, registry, entry))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def validate_no_unbound_vars(symbol_ref, registry, entry)
|
|
25
|
+
actual_symbol = registry.lookup(symbol_ref)
|
|
26
|
+
return [] unless actual_symbol.body
|
|
27
|
+
|
|
28
|
+
missing = collect_vars(actual_symbol.body, registry)
|
|
29
|
+
.group_by(&:name)
|
|
30
|
+
.reject { |name, _| actual_symbol.type_params.map(&:name).include?(name) }
|
|
31
|
+
.values
|
|
32
|
+
.flatten
|
|
33
|
+
|
|
34
|
+
return [] if missing.empty?
|
|
35
|
+
|
|
36
|
+
[
|
|
37
|
+
Error::UnboundTypeVariable.new(
|
|
38
|
+
entry.name,
|
|
39
|
+
missing.size == 1 ? missing.first.decl_span : actual_symbol.decl_span,
|
|
40
|
+
type_name: symbol_ref.name,
|
|
41
|
+
variables: missing.map(&:name).uniq,
|
|
42
|
+
),
|
|
43
|
+
]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -15,6 +15,7 @@ module Jade
|
|
|
15
15
|
.map_node { it.with(symbol: symbol_ref) }
|
|
16
16
|
.add_errors(validate_no_unbound_vars(symbol_ref, registry, entry))
|
|
17
17
|
.add_errors(validate_type_symbol(symbol_ref, registry, entry))
|
|
18
|
+
.add_errors(validate_inhabited(symbol_ref, registry, entry, kind: 'type'))
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
private
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'jade/frontend/semantic_analysis/error'
|
|
2
2
|
|
|
3
|
+
require 'jade/frontend/semantic_analysis/inhabitedness'
|
|
3
4
|
require 'jade/frontend/semantic_analysis/helper'
|
|
4
5
|
require 'jade/frontend/semantic_analysis/module_node'
|
|
5
6
|
require 'jade/frontend/semantic_analysis/import_declaration'
|
|
@@ -19,6 +20,7 @@ require 'jade/frontend/semantic_analysis/interop_import_declaration'
|
|
|
19
20
|
require 'jade/frontend/semantic_analysis/implementation'
|
|
20
21
|
require 'jade/frontend/semantic_analysis/implementation_function'
|
|
21
22
|
require 'jade/frontend/semantic_analysis/struct_declaration'
|
|
23
|
+
require 'jade/frontend/semantic_analysis/type_alias_declaration'
|
|
22
24
|
require 'jade/frontend/semantic_analysis/interface_declaration'
|
|
23
25
|
require 'jade/frontend/semantic_analysis/if_then_else'
|
|
24
26
|
require 'jade/frontend/semantic_analysis/member_access'
|
|
@@ -28,6 +30,7 @@ require 'jade/frontend/semantic_analysis/case_of'
|
|
|
28
30
|
require 'jade/frontend/semantic_analysis/case_of_branch'
|
|
29
31
|
require 'jade/frontend/semantic_analysis/pattern_wildcard'
|
|
30
32
|
require 'jade/frontend/semantic_analysis/pattern_literal'
|
|
33
|
+
require 'jade/frontend/semantic_analysis/pattern_range'
|
|
31
34
|
require 'jade/frontend/semantic_analysis/pattern_binding'
|
|
32
35
|
require 'jade/frontend/semantic_analysis/pattern_constructor'
|
|
33
36
|
require 'jade/frontend/semantic_analysis/pattern_record'
|
|
@@ -86,6 +89,7 @@ module Jade
|
|
|
86
89
|
in AST::Implementation then Implementation
|
|
87
90
|
in AST::ImplementationFunction then ImplementationFunction
|
|
88
91
|
in AST::StructDeclaration then StructDeclaration
|
|
92
|
+
in AST::TypeAliasDeclaration then TypeAliasDeclaration
|
|
89
93
|
in AST::InterfaceDeclaration then InterfaceDeclaration
|
|
90
94
|
in AST::IfThenElse then IfThenElse
|
|
91
95
|
in AST::MemberAccess then MemberAccess
|
|
@@ -95,6 +99,7 @@ module Jade
|
|
|
95
99
|
in AST::CaseOfBranch then CaseOfBranch
|
|
96
100
|
in AST::Pattern::Wildcard then PatternWildcard
|
|
97
101
|
in AST::Pattern::Literal then PatternLiteral
|
|
102
|
+
in AST::Pattern::Range then PatternRange
|
|
98
103
|
in AST::Pattern::Binding then PatternBinding
|
|
99
104
|
in AST::Pattern::Constructor then PatternConstructor
|
|
100
105
|
in AST::Pattern::Record then PatternRecord
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
|
|
3
|
+
module Jade
|
|
4
|
+
module Frontend
|
|
5
|
+
module TypeChecking
|
|
6
|
+
# One mistake is met by every node above it, each reporting it in
|
|
7
|
+
# whatever type the one below left behind. Only the first is news.
|
|
8
|
+
module Cascade
|
|
9
|
+
extend self
|
|
10
|
+
|
|
11
|
+
def prune(errors)
|
|
12
|
+
errors
|
|
13
|
+
.then { one_view_per_span(it) }
|
|
14
|
+
.then { drop_enclosing(it) }
|
|
15
|
+
.then { drop_downstream(it) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
# Parent and child, reporting one unification failure. A subclass
|
|
21
|
+
# of TypeMismatch exists to say more, so it is the one to keep.
|
|
22
|
+
def one_view_per_span(errors)
|
|
23
|
+
errors
|
|
24
|
+
.chunk_while { |a, b| a.span && a.span == b.span }
|
|
25
|
+
.flat_map { |group| mismatches?(group) ? [most_specific(group)] : group }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def mismatches?(group)
|
|
29
|
+
group.size > 1 && group.all? { it.is_a?(Error::TypeMismatch) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def most_specific(group)
|
|
33
|
+
group
|
|
34
|
+
.each_with_index
|
|
35
|
+
.min_by { |error, i| [-depth(error.class), i] }
|
|
36
|
+
.first
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def depth(klass)
|
|
40
|
+
klass.ancestors.take_while { it != Jade::Error }.size
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# A mismatch spanning a mismatch already reported is the node above
|
|
44
|
+
# it complaining about what the one below produced.
|
|
45
|
+
def drop_enclosing(errors)
|
|
46
|
+
errors.reject do |error|
|
|
47
|
+
error.is_a?(Error::TypeMismatch) &&
|
|
48
|
+
errors.any? { encloses?(error, it) }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def encloses?(outer, inner)
|
|
53
|
+
return false unless inner.is_a?(Error::TypeMismatch)
|
|
54
|
+
return false if outer.equal?(inner) || outer.span.nil? || inner.span.nil?
|
|
55
|
+
|
|
56
|
+
outer.span.begin <= inner.span.begin &&
|
|
57
|
+
inner.span.end <= outer.span.end &&
|
|
58
|
+
outer.span != inner.span
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# That variable is the hole the first failure left, so anything
|
|
62
|
+
# naming it fails for the same reason.
|
|
63
|
+
def drop_downstream(errors)
|
|
64
|
+
errors
|
|
65
|
+
.reduce([[], ::Set.new]) do |(kept, seen), error|
|
|
66
|
+
vars = inference_vars(error)
|
|
67
|
+
|
|
68
|
+
next [kept, seen] if vars.any? { seen.include?(it) }
|
|
69
|
+
|
|
70
|
+
[kept + [error], seen | vars]
|
|
71
|
+
end
|
|
72
|
+
.first
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Two errors mentioning the author's `a` are not thereby the same
|
|
76
|
+
# error; only inference's own variables mark a shared failure.
|
|
77
|
+
def inference_vars(error)
|
|
78
|
+
return ::Set.new unless error.is_a?(Error::TypeMismatch)
|
|
79
|
+
|
|
80
|
+
[error.expected, error.actual]
|
|
81
|
+
.compact
|
|
82
|
+
.flat_map(&:unbound_vars)
|
|
83
|
+
.select { it.name.nil? }
|
|
84
|
+
.map(&:id)
|
|
85
|
+
.to_set
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -47,6 +47,9 @@ module Jade
|
|
|
47
47
|
failed(constraint, entry_name)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
in Type::AnonymousRecord(fields:)
|
|
51
|
+
derive_anonymous_record(constraint, fields, lookup, entry_name)
|
|
52
|
+
|
|
50
53
|
else
|
|
51
54
|
failed(constraint, entry_name)
|
|
52
55
|
end
|
|
@@ -93,7 +96,14 @@ module Jade
|
|
|
93
96
|
|
|
94
97
|
def derive_struct(constraint, struct_sym, type_args, registry, lookup, entry_name)
|
|
95
98
|
fields = struct_fields(struct_sym, type_args, registry)
|
|
99
|
+
derive_record(constraint, fields, lookup, entry_name)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def derive_anonymous_record(constraint, fields, lookup, entry_name)
|
|
103
|
+
derive_record(constraint, fields.to_a, lookup, entry_name)
|
|
104
|
+
end
|
|
96
105
|
|
|
106
|
+
def derive_record(constraint, fields, lookup, entry_name)
|
|
97
107
|
field_deps = fields
|
|
98
108
|
.map { |_, field_type| Type.constraint(INTERFACE, field_type, nil) }
|
|
99
109
|
|
|
@@ -2,7 +2,6 @@ require_relative './deriving/helpers.rb'
|
|
|
2
2
|
require_relative './deriving/eq.rb'
|
|
3
3
|
require_relative './deriving/decodable.rb'
|
|
4
4
|
require_relative './deriving/encodable.rb'
|
|
5
|
-
require_relative './deriving/assignable.rb'
|
|
6
5
|
require_relative './deriving/show.rb'
|
|
7
6
|
|
|
8
7
|
module Jade
|
|
@@ -12,14 +11,16 @@ module Jade
|
|
|
12
11
|
module Deriving
|
|
13
12
|
extend self
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
BUILTIN = [Eq, Show, Decodable, Encodable].freeze
|
|
15
|
+
|
|
16
|
+
def derivers = BUILTIN + Extensions.derivers
|
|
16
17
|
|
|
17
18
|
def derivable?(interface)
|
|
18
|
-
|
|
19
|
+
derivers.any? { it.supports?(interface) }
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def derive(constraint, registry, entry_name, &lookup)
|
|
22
|
-
|
|
23
|
+
derivers
|
|
23
24
|
.find { it.supports?(constraint.interface) }
|
|
24
25
|
.then { it.derive(constraint, registry, entry_name, &lookup) }
|
|
25
26
|
end
|