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.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +442 -12
  3. data/README.md +1 -0
  4. data/docs/interop.md +18 -1
  5. data/docs/json.md +34 -3
  6. data/docs/stdlib.md +3 -1
  7. data/docs/syntax.md +96 -0
  8. data/lib/jade/api.rb +9 -0
  9. data/lib/jade/ast.rb +37 -1
  10. data/lib/jade/cli/eject.rb +132 -0
  11. data/lib/jade/cli/init.rb +86 -0
  12. data/lib/jade/cli.rb +4 -0
  13. data/lib/jade/codegen/boundary/specialized/list.rb +11 -7
  14. data/lib/jade/codegen/boundary/specialized/maybe.rb +2 -2
  15. data/lib/jade/codegen/boundary/specialized/record.rb +19 -4
  16. data/lib/jade/codegen/boundary/specialized/scalar.rb +2 -2
  17. data/lib/jade/codegen/boundary/specialized.rb +13 -5
  18. data/lib/jade/codegen/boundary.rb +12 -2
  19. data/lib/jade/codegen/context.rb +17 -0
  20. data/lib/jade/codegen/error.rb +1 -0
  21. data/lib/jade/codegen/function_call.rb +38 -7
  22. data/lib/jade/codegen/function_declaration.rb +25 -13
  23. data/lib/jade/codegen/helpers.rb +27 -4
  24. data/lib/jade/codegen/implementation.rb +2 -2
  25. data/lib/jade/codegen/inline.rb +1 -1
  26. data/lib/jade/codegen/inlines.rb +6 -1
  27. data/lib/jade/codegen/names.rb +31 -0
  28. data/lib/jade/codegen/pattern/constructor.rb +2 -2
  29. data/lib/jade/codegen/transforms/fold_shape.rb +1 -1
  30. data/lib/jade/codegen.rb +76 -15
  31. data/lib/jade/compiler.rb +2 -0
  32. data/lib/jade/entry.rb +1 -1
  33. data/lib/jade/formatter/calls.rb +68 -11
  34. data/lib/jade/formatter/collections.rb +4 -4
  35. data/lib/jade/formatter/declarations.rb +13 -15
  36. data/lib/jade/formatter/helper.rb +20 -0
  37. data/lib/jade/formatter/infix_application.rb +19 -4
  38. data/lib/jade/formatter/lambda.rb +24 -12
  39. data/lib/jade/formatter/pattern.rb +3 -0
  40. data/lib/jade/formatter/type.rb +3 -5
  41. data/lib/jade/frontend/desugaring/placeholder.rb +40 -17
  42. data/lib/jade/frontend/desugaring.rb +29 -9
  43. data/lib/jade/frontend/fixity_fixer.rb +6 -4
  44. data/lib/jade/frontend/forward_declaration/error/alias_expansion.rb +23 -0
  45. data/lib/jade/frontend/forward_declaration/error/duplicate_constructor_name.rb +45 -0
  46. data/lib/jade/frontend/forward_declaration/error/duplicate_type_name.rb +42 -0
  47. data/lib/jade/frontend/forward_declaration/error.rb +3 -0
  48. data/lib/jade/frontend/forward_declaration/helper.rb +58 -0
  49. data/lib/jade/frontend/forward_declaration/import_declaration.rb +4 -0
  50. data/lib/jade/frontend/forward_declaration/interface_declaration.rb +6 -0
  51. data/lib/jade/frontend/forward_declaration/module.rb +4 -0
  52. data/lib/jade/frontend/forward_declaration/struct_declaration.rb +10 -1
  53. data/lib/jade/frontend/forward_declaration/type_alias_declaration.rb +37 -0
  54. data/lib/jade/frontend/forward_declaration/type_declaration.rb +10 -1
  55. data/lib/jade/frontend/forward_declaration.rb +2 -0
  56. data/lib/jade/frontend/pattern_analysis/exhaustiveness.rb +47 -43
  57. data/lib/jade/frontend/pattern_analysis/matrix.rb +28 -219
  58. data/lib/jade/frontend/pattern_analysis/pattern.rb +132 -0
  59. data/lib/jade/frontend/pattern_analysis/redundancy.rb +24 -0
  60. data/lib/jade/frontend/pattern_analysis/signature.rb +220 -0
  61. data/lib/jade/frontend/pattern_analysis/usefulness.rb +72 -0
  62. data/lib/jade/frontend/pattern_analysis/witnesses.rb +73 -0
  63. data/lib/jade/frontend/pattern_analysis.rb +5 -38
  64. data/lib/jade/frontend/semantic_analysis/error/implementation_on_alias.rb +24 -0
  65. data/lib/jade/frontend/semantic_analysis/error/no_base_case.rb +45 -0
  66. data/lib/jade/frontend/semantic_analysis/error/placeholder_not_allowed.rb +15 -3
  67. data/lib/jade/frontend/semantic_analysis/error/recursive_type_alias.rb +24 -0
  68. data/lib/jade/frontend/semantic_analysis/error.rb +3 -0
  69. data/lib/jade/frontend/semantic_analysis/helper.rb +31 -0
  70. data/lib/jade/frontend/semantic_analysis/implementation.rb +13 -0
  71. data/lib/jade/frontend/semantic_analysis/inhabitedness.rb +90 -0
  72. data/lib/jade/frontend/semantic_analysis/interop_import_declaration.rb +28 -16
  73. data/lib/jade/frontend/semantic_analysis/keyed_call.rb +21 -5
  74. data/lib/jade/frontend/semantic_analysis/pattern_range.rb +16 -0
  75. data/lib/jade/frontend/semantic_analysis/struct_declaration.rb +1 -0
  76. data/lib/jade/frontend/semantic_analysis/type_alias_declaration/cycle_detection.rb +95 -0
  77. data/lib/jade/frontend/semantic_analysis/type_alias_declaration.rb +48 -0
  78. data/lib/jade/frontend/semantic_analysis/type_declaration.rb +1 -0
  79. data/lib/jade/frontend/semantic_analysis.rb +5 -0
  80. data/lib/jade/frontend/type_checking/canonicalize.rb +1 -0
  81. data/lib/jade/frontend/type_checking/cascade.rb +90 -0
  82. data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +10 -0
  83. data/lib/jade/frontend/type_checking/constraints.rb +18 -3
  84. data/lib/jade/frontend/type_checking/error/display.rb +49 -0
  85. data/lib/jade/frontend/type_checking/error/division_by_zero.rb +31 -0
  86. data/lib/jade/frontend/type_checking/error/empty_range_pattern.rb +25 -0
  87. data/lib/jade/frontend/type_checking/error/function_body_type_mismatch.rb +4 -3
  88. data/lib/jade/frontend/type_checking/error/function_call_type_mismatch.rb +108 -5
  89. data/lib/jade/frontend/type_checking/error/if_branch_type_mismatch.rb +1 -1
  90. data/lib/jade/frontend/type_checking/error/if_branches_type_mismatch.rb +1 -1
  91. data/lib/jade/frontend/type_checking/error/if_condition_type_mismatch.rb +1 -1
  92. data/lib/jade/frontend/type_checking/error/implementation_type_mismatch.rb +1 -1
  93. data/lib/jade/frontend/type_checking/error/list_item_type_mismatch.rb +1 -1
  94. data/lib/jade/frontend/type_checking/error/missing_patterns.rb +20 -4
  95. data/lib/jade/frontend/type_checking/error/pattern_type_mismatch.rb +7 -1
  96. data/lib/jade/frontend/type_checking/error/range_pattern_type.rb +29 -0
  97. data/lib/jade/frontend/type_checking/error/record_access_type_mismatch.rb +2 -2
  98. data/lib/jade/frontend/type_checking/error/record_update_type_mismatch.rb +21 -0
  99. data/lib/jade/frontend/type_checking/error/recursive_derivation.rb +25 -0
  100. data/lib/jade/frontend/type_checking/error/type_mismatch.rb +10 -2
  101. data/lib/jade/frontend/type_checking/error/unreachable_branch.rb +22 -0
  102. data/lib/jade/frontend/type_checking/error.rb +7 -0
  103. data/lib/jade/frontend/type_checking/inference/assign.rb +1 -1
  104. data/lib/jade/frontend/type_checking/inference/case_of.rb +21 -4
  105. data/lib/jade/frontend/type_checking/inference/division.rb +50 -0
  106. data/lib/jade/frontend/type_checking/inference/function_call.rb +26 -1
  107. data/lib/jade/frontend/type_checking/inference/lambda.rb +1 -1
  108. data/lib/jade/frontend/type_checking/inference/pattern.rb +35 -0
  109. data/lib/jade/frontend/type_checking/inference/record_update.rb +8 -1
  110. data/lib/jade/frontend/type_checking/inference/type_alias_declaration.rb +18 -0
  111. data/lib/jade/frontend/type_checking/inference.rb +2 -0
  112. data/lib/jade/frontend/type_checking/loader.rb +11 -0
  113. data/lib/jade/frontend/type_checking/port_resolution.rb +26 -8
  114. data/lib/jade/frontend/type_checking/substitution.rb +2 -2
  115. data/lib/jade/frontend/type_checking.rb +3 -1
  116. data/lib/jade/frontend/usage_analysis.rb +5 -1
  117. data/lib/jade/interop/boundary.rb +64 -24
  118. data/lib/jade/interop/error.rb +35 -7
  119. data/lib/jade/interop/lowering.rb +8 -2
  120. data/lib/jade/lsp/converters.rb +83 -2
  121. data/lib/jade/lsp/handlers.rb +122 -25
  122. data/lib/jade/lsp/snippets.rb +12 -0
  123. data/lib/jade/lsp/state.rb +11 -4
  124. data/lib/jade/module_loader/build.rb +36 -0
  125. data/lib/jade/module_loader.rb +1 -0
  126. data/lib/jade/parsing/combinators.rb +78 -2
  127. data/lib/jade/parsing/error.rb +43 -6
  128. data/lib/jade/parsing/type.rb +6 -4
  129. data/lib/jade/parsing.rb +140 -26
  130. data/lib/jade/runtime.rb +32 -13
  131. data/lib/jade/source.rb +6 -2
  132. data/lib/jade/stdlib/basics.rb +22 -3
  133. data/lib/jade/stdlib/char.rb +2 -0
  134. data/lib/jade/stdlib/clock.rb +6 -2
  135. data/lib/jade/stdlib/decimal.rb +51 -14
  136. data/lib/jade/stdlib/decode.rb +11 -0
  137. data/lib/jade/stdlib/intrinsics.rb +59 -6
  138. data/lib/jade/stdlib/list.rb +37 -1
  139. data/lib/jade/stdlib/number.rb +47 -0
  140. data/lib/jade/stdlib/range.rb +211 -0
  141. data/lib/jade/stdlib.rb +11 -4
  142. data/lib/jade/symbol/alias.rb +11 -0
  143. data/lib/jade/symbol/parser.rb +1 -1
  144. data/lib/jade/symbol/stdlib_function.rb +1 -1
  145. data/lib/jade/symbol.rb +13 -1
  146. data/lib/jade/task.rb +0 -2
  147. data/lib/jade/type/anonymous_record.rb +11 -2
  148. data/lib/jade/type/application.rb +11 -2
  149. data/lib/jade/type/base.rb +26 -0
  150. data/lib/jade/type/function.rb +11 -3
  151. data/lib/jade/type.rb +36 -10
  152. data/lib/jade/version.rb +1 -1
  153. metadata +35 -2
@@ -6,7 +6,19 @@ module Jade
6
6
  module Constraints
7
7
  extend self
8
8
 
9
- def resolve(constraint, registry, entry_name)
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) { resolve(it, 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's a problem with the body of `#{@function_name}` definition: " ++
13
- "it returns #{@actual} but its signature says it should be #{@expected}"
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(entry, span, expected:, actual:, infix:)
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} but found #{@actual.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} but found #{@actual.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
@@ -10,7 +10,7 @@ module Jade
10
10
 
11
11
  def message
12
12
  "The #{@branch} branch of this if statement is expected to return" +
13
- " #{@expected} but got #{@actual}"
13
+ " #{naming[@expected]} but got #{naming[@actual]}"
14
14
  end
15
15
  end
16
16
  end
@@ -9,7 +9,7 @@ module Jade
9
9
 
10
10
  def message
11
11
  "If branches must return the same type. The then branch produces " +
12
- "#{@expected} but the else branch produces #{@actual}"
12
+ "#{naming[@expected]} but the else branch produces #{naming[@actual]}"
13
13
  end
14
14
  end
15
15
  end
@@ -8,7 +8,7 @@ module Jade
8
8
  end
9
9
 
10
10
  def message
11
- "If condition expects #{@expected} but found #{@actual}"
11
+ "If condition expects #{naming[@expected]} but found #{naming[@actual]}"
12
12
  end
13
13
  end
14
14
  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
@@ -10,7 +10,7 @@ module Jade
10
10
 
11
11
  def message
12
12
  "The #{ordinal(@actual_index)} item does not match the previous items in the list, " +
13
- "expected #{@expected} but found #{@actual}"
13
+ "expected #{naming[@expected]} but found #{naming[@actual]}"
14
14
  end
15
15
  end
16
16
  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
- patterns_str = @missing_patterns
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
- def label
20
- "non-exhaustive pattern match"
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, registry, expr_result.type)
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| [check_exhaustiveness(node, st, registry, expr_result), 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
- def check_exhaustiveness(node, state, registry, result)
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
- PatternAnalysis::Exhaustiveness
85
- .assert(patterns, node.range, state.env, registry, type)
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