jade-lang 0.9.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +442 -12
- data/README.md +1 -0
- data/docs/interop.md +18 -1
- data/docs/json.md +34 -3
- data/docs/stdlib.md +3 -1
- data/docs/syntax.md +96 -0
- data/lib/jade/api.rb +9 -0
- data/lib/jade/ast.rb +37 -1
- data/lib/jade/cli/eject.rb +132 -0
- data/lib/jade/cli/init.rb +86 -0
- data/lib/jade/cli.rb +4 -0
- data/lib/jade/codegen/boundary/specialized/list.rb +11 -7
- data/lib/jade/codegen/boundary/specialized/maybe.rb +2 -2
- data/lib/jade/codegen/boundary/specialized/record.rb +19 -4
- data/lib/jade/codegen/boundary/specialized/scalar.rb +2 -2
- data/lib/jade/codegen/boundary/specialized.rb +13 -5
- data/lib/jade/codegen/boundary.rb +12 -2
- data/lib/jade/codegen/context.rb +17 -0
- data/lib/jade/codegen/error.rb +1 -0
- data/lib/jade/codegen/function_call.rb +38 -7
- data/lib/jade/codegen/function_declaration.rb +25 -13
- data/lib/jade/codegen/helpers.rb +27 -4
- data/lib/jade/codegen/implementation.rb +2 -2
- data/lib/jade/codegen/inline.rb +1 -1
- data/lib/jade/codegen/inlines.rb +6 -1
- data/lib/jade/codegen/names.rb +31 -0
- data/lib/jade/codegen/pattern/constructor.rb +2 -2
- data/lib/jade/codegen/transforms/fold_shape.rb +1 -1
- data/lib/jade/codegen.rb +76 -15
- data/lib/jade/compiler.rb +2 -0
- data/lib/jade/entry.rb +1 -1
- data/lib/jade/formatter/calls.rb +68 -11
- data/lib/jade/formatter/collections.rb +4 -4
- data/lib/jade/formatter/declarations.rb +13 -15
- data/lib/jade/formatter/helper.rb +20 -0
- data/lib/jade/formatter/infix_application.rb +19 -4
- data/lib/jade/formatter/lambda.rb +24 -12
- data/lib/jade/formatter/pattern.rb +3 -0
- data/lib/jade/formatter/type.rb +3 -5
- data/lib/jade/frontend/desugaring/placeholder.rb +40 -17
- data/lib/jade/frontend/desugaring.rb +29 -9
- data/lib/jade/frontend/fixity_fixer.rb +6 -4
- data/lib/jade/frontend/forward_declaration/error/alias_expansion.rb +23 -0
- data/lib/jade/frontend/forward_declaration/error/duplicate_constructor_name.rb +45 -0
- data/lib/jade/frontend/forward_declaration/error/duplicate_type_name.rb +42 -0
- data/lib/jade/frontend/forward_declaration/error.rb +3 -0
- data/lib/jade/frontend/forward_declaration/helper.rb +58 -0
- data/lib/jade/frontend/forward_declaration/import_declaration.rb +4 -0
- data/lib/jade/frontend/forward_declaration/interface_declaration.rb +6 -0
- data/lib/jade/frontend/forward_declaration/module.rb +4 -0
- data/lib/jade/frontend/forward_declaration/struct_declaration.rb +10 -1
- data/lib/jade/frontend/forward_declaration/type_alias_declaration.rb +37 -0
- data/lib/jade/frontend/forward_declaration/type_declaration.rb +10 -1
- data/lib/jade/frontend/forward_declaration.rb +2 -0
- data/lib/jade/frontend/pattern_analysis/exhaustiveness.rb +47 -43
- data/lib/jade/frontend/pattern_analysis/matrix.rb +28 -219
- data/lib/jade/frontend/pattern_analysis/pattern.rb +132 -0
- data/lib/jade/frontend/pattern_analysis/redundancy.rb +24 -0
- data/lib/jade/frontend/pattern_analysis/signature.rb +220 -0
- data/lib/jade/frontend/pattern_analysis/usefulness.rb +72 -0
- data/lib/jade/frontend/pattern_analysis/witnesses.rb +73 -0
- data/lib/jade/frontend/pattern_analysis.rb +5 -38
- data/lib/jade/frontend/semantic_analysis/error/implementation_on_alias.rb +24 -0
- data/lib/jade/frontend/semantic_analysis/error/no_base_case.rb +45 -0
- data/lib/jade/frontend/semantic_analysis/error/placeholder_not_allowed.rb +15 -3
- data/lib/jade/frontend/semantic_analysis/error/recursive_type_alias.rb +24 -0
- data/lib/jade/frontend/semantic_analysis/error.rb +3 -0
- data/lib/jade/frontend/semantic_analysis/helper.rb +31 -0
- data/lib/jade/frontend/semantic_analysis/implementation.rb +13 -0
- data/lib/jade/frontend/semantic_analysis/inhabitedness.rb +90 -0
- data/lib/jade/frontend/semantic_analysis/interop_import_declaration.rb +28 -16
- data/lib/jade/frontend/semantic_analysis/keyed_call.rb +21 -5
- data/lib/jade/frontend/semantic_analysis/pattern_range.rb +16 -0
- data/lib/jade/frontend/semantic_analysis/struct_declaration.rb +1 -0
- data/lib/jade/frontend/semantic_analysis/type_alias_declaration/cycle_detection.rb +95 -0
- data/lib/jade/frontend/semantic_analysis/type_alias_declaration.rb +48 -0
- data/lib/jade/frontend/semantic_analysis/type_declaration.rb +1 -0
- data/lib/jade/frontend/semantic_analysis.rb +5 -0
- data/lib/jade/frontend/type_checking/canonicalize.rb +1 -0
- data/lib/jade/frontend/type_checking/cascade.rb +90 -0
- data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +10 -0
- data/lib/jade/frontend/type_checking/constraints.rb +18 -3
- data/lib/jade/frontend/type_checking/error/display.rb +49 -0
- data/lib/jade/frontend/type_checking/error/division_by_zero.rb +31 -0
- data/lib/jade/frontend/type_checking/error/empty_range_pattern.rb +25 -0
- data/lib/jade/frontend/type_checking/error/function_body_type_mismatch.rb +4 -3
- data/lib/jade/frontend/type_checking/error/function_call_type_mismatch.rb +108 -5
- data/lib/jade/frontend/type_checking/error/if_branch_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/if_branches_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/if_condition_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/implementation_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/list_item_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/missing_patterns.rb +20 -4
- data/lib/jade/frontend/type_checking/error/pattern_type_mismatch.rb +7 -1
- data/lib/jade/frontend/type_checking/error/range_pattern_type.rb +29 -0
- data/lib/jade/frontend/type_checking/error/record_access_type_mismatch.rb +2 -2
- data/lib/jade/frontend/type_checking/error/record_update_type_mismatch.rb +21 -0
- data/lib/jade/frontend/type_checking/error/recursive_derivation.rb +25 -0
- data/lib/jade/frontend/type_checking/error/type_mismatch.rb +10 -2
- data/lib/jade/frontend/type_checking/error/unreachable_branch.rb +22 -0
- data/lib/jade/frontend/type_checking/error.rb +7 -0
- data/lib/jade/frontend/type_checking/inference/assign.rb +1 -1
- data/lib/jade/frontend/type_checking/inference/case_of.rb +21 -4
- data/lib/jade/frontend/type_checking/inference/division.rb +50 -0
- data/lib/jade/frontend/type_checking/inference/function_call.rb +26 -1
- data/lib/jade/frontend/type_checking/inference/lambda.rb +1 -1
- data/lib/jade/frontend/type_checking/inference/pattern.rb +35 -0
- data/lib/jade/frontend/type_checking/inference/record_update.rb +8 -1
- data/lib/jade/frontend/type_checking/inference/type_alias_declaration.rb +18 -0
- data/lib/jade/frontend/type_checking/inference.rb +2 -0
- data/lib/jade/frontend/type_checking/loader.rb +11 -0
- data/lib/jade/frontend/type_checking/port_resolution.rb +26 -8
- data/lib/jade/frontend/type_checking/substitution.rb +2 -2
- data/lib/jade/frontend/type_checking.rb +3 -1
- data/lib/jade/frontend/usage_analysis.rb +5 -1
- data/lib/jade/interop/boundary.rb +64 -24
- data/lib/jade/interop/error.rb +35 -7
- data/lib/jade/interop/lowering.rb +8 -2
- data/lib/jade/lsp/converters.rb +83 -2
- data/lib/jade/lsp/handlers.rb +122 -25
- data/lib/jade/lsp/snippets.rb +12 -0
- data/lib/jade/lsp/state.rb +11 -4
- data/lib/jade/module_loader/build.rb +36 -0
- data/lib/jade/module_loader.rb +1 -0
- data/lib/jade/parsing/combinators.rb +78 -2
- data/lib/jade/parsing/error.rb +43 -6
- data/lib/jade/parsing/type.rb +6 -4
- data/lib/jade/parsing.rb +140 -26
- data/lib/jade/runtime.rb +32 -13
- data/lib/jade/source.rb +6 -2
- data/lib/jade/stdlib/basics.rb +22 -3
- data/lib/jade/stdlib/char.rb +2 -0
- data/lib/jade/stdlib/clock.rb +6 -2
- data/lib/jade/stdlib/decimal.rb +51 -14
- data/lib/jade/stdlib/decode.rb +11 -0
- data/lib/jade/stdlib/intrinsics.rb +59 -6
- data/lib/jade/stdlib/list.rb +37 -1
- data/lib/jade/stdlib/number.rb +47 -0
- data/lib/jade/stdlib/range.rb +211 -0
- data/lib/jade/stdlib.rb +11 -4
- data/lib/jade/symbol/alias.rb +11 -0
- data/lib/jade/symbol/parser.rb +1 -1
- data/lib/jade/symbol/stdlib_function.rb +1 -1
- data/lib/jade/symbol.rb +13 -1
- data/lib/jade/task.rb +0 -2
- data/lib/jade/type/anonymous_record.rb +11 -2
- data/lib/jade/type/application.rb +11 -2
- data/lib/jade/type/base.rb +26 -0
- data/lib/jade/type/function.rb +11 -3
- data/lib/jade/type.rb +36 -10
- data/lib/jade/version.rb +1 -1
- metadata +35 -2
|
@@ -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
|
|
@@ -3,21 +3,37 @@ module Jade
|
|
|
3
3
|
module TypeChecking
|
|
4
4
|
module Error
|
|
5
5
|
class MissingPatterns < Jade::Error
|
|
6
|
+
LISTED = 10
|
|
7
|
+
|
|
6
8
|
def initialize(entry, span, missing_patterns:)
|
|
7
9
|
@missing_patterns = missing_patterns
|
|
8
10
|
super(entry:, span:)
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
def message
|
|
12
|
-
|
|
14
|
+
"Pattern match is not exhaustive. Missing cases:\n#{listed}#{rest}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def label
|
|
18
|
+
"non-exhaustive pattern match"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def listed
|
|
24
|
+
@missing_patterns
|
|
25
|
+
.take(LISTED)
|
|
13
26
|
.map { |row| row.map(&:to_s).join(', ') }
|
|
14
27
|
.map { " #{it}" }
|
|
15
28
|
.join("\n")
|
|
16
|
-
"Pattern match is not exhaustive. Missing cases:\n#{patterns_str}"
|
|
17
29
|
end
|
|
18
30
|
|
|
19
|
-
|
|
20
|
-
|
|
31
|
+
# A table split at every bound can leave hundreds of holes, and a
|
|
32
|
+
# three-hundred-line error is not one anybody reads.
|
|
33
|
+
def rest
|
|
34
|
+
return '' if @missing_patterns.size <= LISTED
|
|
35
|
+
|
|
36
|
+
"\n … and #{@missing_patterns.size - LISTED} more"
|
|
21
37
|
end
|
|
22
38
|
end
|
|
23
39
|
end
|
|
@@ -2,9 +2,15 @@ module Jade
|
|
|
2
2
|
module Frontend
|
|
3
3
|
module TypeChecking
|
|
4
4
|
module Error
|
|
5
|
+
# A pattern the checker could not make sense of. Coverage is a claim
|
|
6
|
+
# about the patterns as written, so one of these silences it.
|
|
7
|
+
module PatternProblem; end
|
|
8
|
+
|
|
5
9
|
class PatternTypeMismatch < TypeMismatch
|
|
10
|
+
include PatternProblem
|
|
11
|
+
|
|
6
12
|
def message
|
|
7
|
-
"Pattern is trying to match #{@expected} with #{@actual}"
|
|
13
|
+
"Pattern is trying to match #{naming[@expected]} with #{naming[@actual]}"
|
|
8
14
|
end
|
|
9
15
|
end
|
|
10
16
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module TypeChecking
|
|
4
|
+
module Error
|
|
5
|
+
class RangePatternType < PatternTypeMismatch
|
|
6
|
+
def message
|
|
7
|
+
"A range pattern only matches Int, and this one is matching " \
|
|
8
|
+
"#{naming[@expected]}."
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def label
|
|
12
|
+
'range pattern on a type that is not Int'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def notes
|
|
16
|
+
[
|
|
17
|
+
Jade::Diagnostics::Annotation[
|
|
18
|
+
:note,
|
|
19
|
+
'Int is the only type a range covers checkably: 0..2 and ' \
|
|
20
|
+
'3..12 sit next to each other with nothing in between ' \
|
|
21
|
+
'them to be missed',
|
|
22
|
+
],
|
|
23
|
+
]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -4,8 +4,8 @@ module Jade
|
|
|
4
4
|
module Error
|
|
5
5
|
class RecordAccessTypeMismatch < TypeMismatch
|
|
6
6
|
def message
|
|
7
|
-
"Something is off with this record access, it expects #{@expected} " +
|
|
8
|
-
"but found #{@actual}"
|
|
7
|
+
"Something is off with this record access, it expects #{naming[@expected]} " +
|
|
8
|
+
"but found #{naming[@actual]}"
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module TypeChecking
|
|
4
|
+
module Error
|
|
5
|
+
# An update produces a record of its own, which then has to be the
|
|
6
|
+
# record the surrounding code wanted. Changing a field's type is the
|
|
7
|
+
# usual way to break that, and any other disagreement lands here too.
|
|
8
|
+
class RecordUpdateTypeMismatch < TypeMismatch
|
|
9
|
+
def message
|
|
10
|
+
"This update produces #{naming.annotated(@actual)}, " \
|
|
11
|
+
"but #{naming.annotated(@expected)} was expected"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def label
|
|
15
|
+
"produces #{naming[@actual]}, expected #{naming[@expected]}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module TypeChecking
|
|
4
|
+
module Error
|
|
5
|
+
class RecursiveDerivation < Jade::Error
|
|
6
|
+
attr_reader :constraint
|
|
7
|
+
|
|
8
|
+
def initialize(entry, span, constraint:)
|
|
9
|
+
@constraint = constraint
|
|
10
|
+
super(entry:, span:)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def message
|
|
14
|
+
"#{@constraint.interface} cannot be derived for #{@constraint.type} " \
|
|
15
|
+
'because it contains itself; write the implementation by hand'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def label
|
|
19
|
+
"#{@constraint.type} is recursive"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -10,11 +10,19 @@ module Jade
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def message
|
|
13
|
-
"Expected #{@expected} but got #{@actual}"
|
|
13
|
+
"Expected #{naming.annotated(@expected)} but got #{naming.annotated(@actual)}"
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def label
|
|
17
|
-
"expected #{@expected}, got #{@actual}"
|
|
17
|
+
"expected #{naming[@expected]}, got #{naming[@actual]}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_reader :expected, :actual
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def naming
|
|
25
|
+
@naming ||= Display.naming(@expected, @actual)
|
|
18
26
|
end
|
|
19
27
|
end
|
|
20
28
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module TypeChecking
|
|
4
|
+
module Error
|
|
5
|
+
class UnreachableBranch < Jade::Error
|
|
6
|
+
def initialize(entry, span)
|
|
7
|
+
super(entry:, span:)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def message
|
|
11
|
+
'This branch can never match. The branches above it already cover ' \
|
|
12
|
+
'every value that would reach it.'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def label
|
|
16
|
+
'unreachable branch'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
require 'jade/error'
|
|
2
2
|
|
|
3
|
+
require 'jade/frontend/type_checking/error/display'
|
|
3
4
|
require 'jade/frontend/type_checking/error/type_mismatch'
|
|
4
5
|
|
|
5
6
|
require 'jade/frontend/type_checking/error/case_of_branches_type_mismatch'
|
|
6
7
|
require 'jade/frontend/type_checking/error/derivation_failed'
|
|
8
|
+
require 'jade/frontend/type_checking/error/recursive_derivation'
|
|
7
9
|
require 'jade/frontend/type_checking/error/function_body_type_mismatch'
|
|
8
10
|
require 'jade/frontend/type_checking/error/function_call_type_mismatch'
|
|
9
11
|
require 'jade/frontend/type_checking/error/if_branch_type_mismatch'
|
|
10
12
|
require 'jade/frontend/type_checking/error/if_branches_type_mismatch'
|
|
11
13
|
require 'jade/frontend/type_checking/error/if_condition_type_mismatch'
|
|
12
14
|
require 'jade/frontend/type_checking/error/missing_patterns'
|
|
15
|
+
require 'jade/frontend/type_checking/error/division_by_zero'
|
|
16
|
+
require 'jade/frontend/type_checking/error/unreachable_branch'
|
|
13
17
|
require 'jade/frontend/type_checking/error/list_item_type_mismatch'
|
|
14
18
|
require 'jade/frontend/type_checking/error/pattern_type_mismatch'
|
|
19
|
+
require 'jade/frontend/type_checking/error/empty_range_pattern'
|
|
20
|
+
require 'jade/frontend/type_checking/error/range_pattern_type'
|
|
15
21
|
require 'jade/frontend/type_checking/error/record_access_type_mismatch'
|
|
22
|
+
require 'jade/frontend/type_checking/error/record_update_type_mismatch'
|
|
16
23
|
require 'jade/frontend/type_checking/error/unresolved_constraint'
|
|
17
24
|
require 'jade/frontend/type_checking/error/implementation_type_mismatch'
|
|
18
25
|
require 'jade/frontend/type_checking/error/missing_implementation'
|
|
@@ -34,7 +34,7 @@ module Jade
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
PatternAnalysis::Exhaustiveness
|
|
37
|
-
.assert([pattern], pattern.range, final_state.env,
|
|
37
|
+
.assert([pattern], pattern.range, final_state.env, expr_result.type)
|
|
38
38
|
.then { final_state.add_errors(it) }
|
|
39
39
|
.unify_result(expr_result.with(constraints: residual_cs), expected.type)
|
|
40
40
|
end
|
|
@@ -46,7 +46,7 @@ module Jade
|
|
|
46
46
|
expr_result,
|
|
47
47
|
)
|
|
48
48
|
end
|
|
49
|
-
.then { |st, cs| [
|
|
49
|
+
.then { |st, cs| [check_patterns(node, st, expr_result), cs] }
|
|
50
50
|
.then do |st, cs|
|
|
51
51
|
first_result
|
|
52
52
|
.with(constraints: cs)
|
|
@@ -75,17 +75,34 @@ module Jade
|
|
|
75
75
|
)
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
# Coverage is a claim about the patterns as written. When one of them
|
|
79
|
+
# failed to type there is nothing coherent to claim, and saying it
|
|
80
|
+
# anyway buries the real error under invented ones.
|
|
81
|
+
def check_patterns(node, state, result)
|
|
79
82
|
node => AST::CaseOf(branches:)
|
|
80
83
|
|
|
84
|
+
return state if mistyped_patterns?(node, state)
|
|
85
|
+
|
|
81
86
|
patterns = branches.map(&:pattern)
|
|
82
87
|
type = result.apply(state.env.substitution).type
|
|
83
88
|
|
|
84
|
-
|
|
85
|
-
.assert(patterns, node.range, state.env,
|
|
89
|
+
[
|
|
90
|
+
PatternAnalysis::Exhaustiveness.assert(patterns, node.range, state.env, type),
|
|
91
|
+
PatternAnalysis::Redundancy.assert(patterns, state.env, type),
|
|
92
|
+
]
|
|
93
|
+
.flatten
|
|
86
94
|
.then { state.add_errors(it) }
|
|
87
95
|
end
|
|
88
96
|
|
|
97
|
+
def mistyped_patterns?(node, state)
|
|
98
|
+
state
|
|
99
|
+
.errors
|
|
100
|
+
.any? do |error|
|
|
101
|
+
error.is_a?(Error::PatternProblem) &&
|
|
102
|
+
node.range.cover?(error.span.begin)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
89
106
|
def infer_branch(node, registry, env, expected, expression_result)
|
|
90
107
|
node => AST::CaseOfBranch(pattern:, body:)
|
|
91
108
|
|