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,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
|
|
|
@@ -6,7 +6,19 @@ module Jade
|
|
|
6
6
|
module Constraints
|
|
7
7
|
extend self
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
# A derived instance is built by asking for its components' instances,
|
|
10
|
+
# so a type that contains itself asks for the one being built. Nothing
|
|
11
|
+
# ties that knot yet, so the walk is cut here rather than left to run
|
|
12
|
+
# out of stack.
|
|
13
|
+
def resolve(constraint, registry, entry_name, in_progress = ::Set[])
|
|
14
|
+
key = [constraint.interface, constraint.type.to_s]
|
|
15
|
+
|
|
16
|
+
if in_progress.include?(key)
|
|
17
|
+
Error::RecursiveDerivation
|
|
18
|
+
.new(entry_name, constraint.origin&.range, constraint:)
|
|
19
|
+
.then { return Err[it] }
|
|
20
|
+
end
|
|
21
|
+
|
|
10
22
|
if constraint.type in Type::Var
|
|
11
23
|
Error::UnresolvedConstraint
|
|
12
24
|
.new(entry_name, constraint.origin&.range, constraint:)
|
|
@@ -23,7 +35,7 @@ module Jade
|
|
|
23
35
|
constraint.type,
|
|
24
36
|
constraint.origin,
|
|
25
37
|
)
|
|
26
|
-
.then { resolve(it, registry, entry_name) } => Ok[resolved]
|
|
38
|
+
.then { resolve(it, registry, entry_name, in_progress) } => Ok[resolved]
|
|
27
39
|
|
|
28
40
|
resolved
|
|
29
41
|
end
|
|
@@ -34,7 +46,9 @@ module Jade
|
|
|
34
46
|
|
|
35
47
|
if Deriving.derivable?(constraint.interface)
|
|
36
48
|
Deriving
|
|
37
|
-
.derive(constraint, registry, entry_name)
|
|
49
|
+
.derive(constraint, registry, entry_name) do
|
|
50
|
+
resolve(it, registry, entry_name, in_progress + [key])
|
|
51
|
+
end
|
|
38
52
|
.then { return it }
|
|
39
53
|
end
|
|
40
54
|
|
|
@@ -87,6 +101,7 @@ module Jade
|
|
|
87
101
|
in Type::Application(constructor:) then constructor.name
|
|
88
102
|
in Type::PartialApplication(constructor:) then constructor.name
|
|
89
103
|
in Type::Constructor(name:) then name
|
|
104
|
+
in Type::Displayable => t then t.render
|
|
90
105
|
else constraint.type.to_s
|
|
91
106
|
end
|
|
92
107
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module TypeChecking
|
|
4
|
+
module Error
|
|
5
|
+
# Inference ids (`t8894`) are counters: they change between
|
|
6
|
+
# compiles, and two occurrences of one variable look unrelated
|
|
7
|
+
# unless you notice the digits match. Read out as `a`, `b`, `c`;
|
|
8
|
+
# the ids themselves stay internal.
|
|
9
|
+
module Display
|
|
10
|
+
extend self
|
|
11
|
+
|
|
12
|
+
LETTERS = ('a'..'z').to_a.freeze
|
|
13
|
+
|
|
14
|
+
Naming = Data.define(:substitution) do
|
|
15
|
+
def [](type)
|
|
16
|
+
substitution.apply(type).to_s
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Spells out the body of an alias, for the one type a message is
|
|
20
|
+
# about. Nested positions keep the short name.
|
|
21
|
+
def annotated(type)
|
|
22
|
+
substitution.apply(type).then do |applied|
|
|
23
|
+
applied.is_a?(Type::Displayable) ? applied.annotated : applied.to_s
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Every type in one message at once, so a variable is the same
|
|
29
|
+
# letter on both sides of it.
|
|
30
|
+
def naming(*types)
|
|
31
|
+
types
|
|
32
|
+
.flat_map(&:unbound_vars)
|
|
33
|
+
.uniq(&:id)
|
|
34
|
+
.select { it.name.nil? }
|
|
35
|
+
.each_with_index
|
|
36
|
+
.to_h { |var, i| [var.id, Type.var(var.id, letter(i))] }
|
|
37
|
+
.then { Naming[Substitution.new(it)] }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def letter(index)
|
|
43
|
+
LETTERS[index] || "t#{index - LETTERS.size + 1}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module TypeChecking
|
|
4
|
+
module Error
|
|
5
|
+
class DivisionByZero < Jade::Error
|
|
6
|
+
def initialize(entry, span)
|
|
7
|
+
super(entry:, span:)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def message
|
|
11
|
+
'Cannot divide by zero.'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def label
|
|
15
|
+
'this divisor is zero'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def notes
|
|
19
|
+
[
|
|
20
|
+
Jade::Diagnostics::Annotation[
|
|
21
|
+
:help,
|
|
22
|
+
'division takes a `NonZero`. A literal other than zero is one; ' \
|
|
23
|
+
'for a value, `non_zero(n)` gives a `Maybe(NonZero(a))`',
|
|
24
|
+
],
|
|
25
|
+
]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module TypeChecking
|
|
4
|
+
module Error
|
|
5
|
+
class EmptyRangePattern < Jade::Error
|
|
6
|
+
include PatternProblem
|
|
7
|
+
def initialize(entry, span, from:, to:)
|
|
8
|
+
@from = from
|
|
9
|
+
@to = to
|
|
10
|
+
super(entry:, span:)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def message
|
|
14
|
+
"#{@from}..#{@to} matches nothing, because #{@from} is greater " \
|
|
15
|
+
"than #{@to}."
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def label
|
|
19
|
+
'empty range'
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -9,12 +9,13 @@ module Jade
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def message
|
|
12
|
-
"There
|
|
13
|
-
"it returns #{@actual} but its signature says it should be
|
|
12
|
+
"There\u0027s a problem with the body of `#{@function_name}` definition: " \
|
|
13
|
+
"it returns #{naming.annotated(@actual)} but its signature says it should be " \
|
|
14
|
+
"#{naming.annotated(@expected)}"
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def label
|
|
17
|
-
"returns #{@actual}, expected #{@expected}"
|
|
18
|
+
"returns #{naming[@actual]}, expected #{naming[@expected]}"
|
|
18
19
|
end
|
|
19
20
|
end
|
|
20
21
|
end
|
|
@@ -3,9 +3,15 @@ module Jade
|
|
|
3
3
|
module TypeChecking
|
|
4
4
|
module Error
|
|
5
5
|
class FunctionCallTypeMismatch < TypeMismatch
|
|
6
|
-
def initialize(
|
|
6
|
+
def initialize(
|
|
7
|
+
entry, span, expected:, actual:, infix:, lifted_placeholder:,
|
|
8
|
+
callee_name:, arg_names:
|
|
9
|
+
)
|
|
7
10
|
super
|
|
8
11
|
@infix = infix
|
|
12
|
+
@lifted_placeholder = lifted_placeholder
|
|
13
|
+
@callee_name = callee_name
|
|
14
|
+
@arg_names = arg_names
|
|
9
15
|
end
|
|
10
16
|
|
|
11
17
|
def message
|
|
@@ -13,22 +19,119 @@ module Jade
|
|
|
13
19
|
in AST::InfixOperator(value:)
|
|
14
20
|
infix_error(value)
|
|
15
21
|
else
|
|
16
|
-
"Function call mismatch, expected #{@expected} but found #{@actual}"
|
|
22
|
+
"Function call mismatch, expected #{naming[@expected]} but found #{naming[@actual]}"
|
|
17
23
|
end
|
|
18
24
|
end
|
|
19
25
|
|
|
26
|
+
def notes
|
|
27
|
+
return non_zero_note if wanted_non_zero?
|
|
28
|
+
return placeholder_note if one_too_many_for_the_placeholder?
|
|
29
|
+
return arity_note if wrong_arity?
|
|
30
|
+
return under_applied_note if under_applied
|
|
31
|
+
|
|
32
|
+
[]
|
|
33
|
+
end
|
|
34
|
+
|
|
20
35
|
private
|
|
21
36
|
|
|
37
|
+
# The only type nobody can construct directly, so saying what was
|
|
38
|
+
# expected is not enough to act on.
|
|
39
|
+
def wanted_non_zero?
|
|
40
|
+
@expected in Type::Function(
|
|
41
|
+
args: [*, Type::Application(constructor: Type::Constructor(name: 'Basics.NonZero'))]
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def non_zero_note
|
|
46
|
+
help(
|
|
47
|
+
'a divisor has to be known not to be zero. A literal other than ' \
|
|
48
|
+
'zero is; for a value, `non_zero(n)` gives a `Maybe(NonZero(a))`',
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def placeholder_note
|
|
53
|
+
('a `_` makes this a function waiting for that argument, and the ' \
|
|
54
|
+
'call has one argument too many for that. If you piped into it, ' \
|
|
55
|
+
'`|>` already supplies the first argument, so drop the `_`')
|
|
56
|
+
.then { help(it) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def arity_note
|
|
60
|
+
"#{subject(@callee_name, 'this')} takes #{count(@expected.args.size)}, " \
|
|
61
|
+
"#{@actual.args.size} given"
|
|
62
|
+
.then { help(it) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# An argument that arrives as a function returning what was wanted
|
|
66
|
+
# is a constructor or function that has not been given everything
|
|
67
|
+
# it needs.
|
|
68
|
+
def under_applied_note
|
|
69
|
+
@actual
|
|
70
|
+
.args[under_applied]
|
|
71
|
+
.args
|
|
72
|
+
.size
|
|
73
|
+
.then { count(it) }
|
|
74
|
+
.then { "#{under_applied_subject} needs #{it}" }
|
|
75
|
+
.then { help(it) }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def under_applied_subject
|
|
79
|
+
subject(@arg_names[under_applied], "argument #{under_applied + 1}")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def under_applied
|
|
83
|
+
return nil unless comparable_calls?
|
|
84
|
+
return nil unless @expected.args.size == @actual.args.size
|
|
85
|
+
|
|
86
|
+
@expected.args.zip(@actual.args).index do |want, got|
|
|
87
|
+
got.is_a?(Type::Function) &&
|
|
88
|
+
!want.is_a?(Type::Function) &&
|
|
89
|
+
got.return_type == want
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def wrong_arity?
|
|
94
|
+
comparable_calls? && @expected.args.size != @actual.args.size
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def comparable_calls?
|
|
98
|
+
!@infix && (@expected in Type::Function) && (@actual in Type::Function)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def subject(name, fallback)
|
|
102
|
+
name ? "`#{name}`" : fallback
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def count(n)
|
|
106
|
+
"#{n} #{n == 1 ? 'argument' : 'arguments'}"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def help(text)
|
|
110
|
+
[Jade::Diagnostics::Annotation[:help, text]]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# `xs |> f(_, y)` reads as though the `_` marks where the piped
|
|
114
|
+
# value goes, but `|>` has already put it in front of it.
|
|
115
|
+
def one_too_many_for_the_placeholder?
|
|
116
|
+
return false unless @lifted_placeholder
|
|
117
|
+
return false unless @expected in Type::Function
|
|
118
|
+
return false unless @actual in Type::Function
|
|
119
|
+
|
|
120
|
+
@actual.args.size == @expected.args.size + 1
|
|
121
|
+
end
|
|
122
|
+
|
|
22
123
|
def infix_error(operator)
|
|
23
124
|
if @expected.args.first != @actual.args.first
|
|
24
|
-
return "Left side of (#{operator}) expects #{@expected.args.first}
|
|
125
|
+
return "Left side of (#{operator}) expects #{naming[@expected.args.first]} " \
|
|
126
|
+
"but found #{naming[@actual.args.first]}"
|
|
25
127
|
end
|
|
26
128
|
|
|
27
129
|
if @expected.args.last != @actual.args.last
|
|
28
|
-
return "Right side of (#{operator}) expects #{@expected.args.last}
|
|
130
|
+
return "Right side of (#{operator}) expects #{naming[@expected.args.last]} " \
|
|
131
|
+
"but found #{naming[@actual.args.last]}"
|
|
29
132
|
end
|
|
30
133
|
|
|
31
|
-
"Function call mismatch, expected #{@expected} but found #{@actual}"
|
|
134
|
+
"Function call mismatch, expected #{naming[@expected]} but found #{naming[@actual]}"
|
|
32
135
|
end
|
|
33
136
|
end
|
|
34
137
|
end
|
|
@@ -11,7 +11,7 @@ module Jade
|
|
|
11
11
|
|
|
12
12
|
def message
|
|
13
13
|
"Implementation of #{@interface}.#{@fn_name}: " \
|
|
14
|
-
"expected #{@expected} but the provided function has type #{@actual}"
|
|
14
|
+
"expected #{naming[@expected]} but the provided function has type #{naming[@actual]}"
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
end
|