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.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +439 -2
  3. data/docs/interop.md +18 -1
  4. data/docs/stdlib.md +3 -1
  5. data/docs/syntax.md +96 -0
  6. data/lib/jade/api.rb +9 -0
  7. data/lib/jade/ast.rb +37 -1
  8. data/lib/jade/cli/eject.rb +132 -0
  9. data/lib/jade/cli.rb +2 -0
  10. data/lib/jade/codegen/boundary/specialized/list.rb +11 -7
  11. data/lib/jade/codegen/boundary/specialized/maybe.rb +2 -2
  12. data/lib/jade/codegen/boundary/specialized/record.rb +19 -4
  13. data/lib/jade/codegen/boundary/specialized/scalar.rb +2 -2
  14. data/lib/jade/codegen/boundary/specialized.rb +13 -5
  15. data/lib/jade/codegen/boundary.rb +12 -2
  16. data/lib/jade/codegen/context.rb +17 -0
  17. data/lib/jade/codegen/error.rb +1 -0
  18. data/lib/jade/codegen/function_call.rb +23 -7
  19. data/lib/jade/codegen/function_declaration.rb +25 -13
  20. data/lib/jade/codegen/helpers.rb +27 -4
  21. data/lib/jade/codegen/implementation.rb +2 -2
  22. data/lib/jade/codegen/inline.rb +1 -1
  23. data/lib/jade/codegen/inlines.rb +6 -1
  24. data/lib/jade/codegen/names.rb +31 -0
  25. data/lib/jade/codegen/pattern/constructor.rb +2 -2
  26. data/lib/jade/codegen/transforms/fold_shape.rb +1 -1
  27. data/lib/jade/codegen.rb +76 -15
  28. data/lib/jade/compiler.rb +2 -0
  29. data/lib/jade/entry.rb +1 -1
  30. data/lib/jade/error.rb +5 -0
  31. data/lib/jade/extensions.rb +70 -0
  32. data/lib/jade/formatter/calls.rb +68 -11
  33. data/lib/jade/formatter/collections.rb +4 -4
  34. data/lib/jade/formatter/declarations.rb +13 -15
  35. data/lib/jade/formatter/helper.rb +20 -0
  36. data/lib/jade/formatter/infix_application.rb +19 -4
  37. data/lib/jade/formatter/lambda.rb +24 -12
  38. data/lib/jade/formatter/pattern.rb +3 -0
  39. data/lib/jade/frontend/desugaring/placeholder.rb +40 -17
  40. data/lib/jade/frontend/desugaring.rb +29 -9
  41. data/lib/jade/frontend/fixity_fixer.rb +6 -4
  42. data/lib/jade/frontend/forward_declaration/error/alias_expansion.rb +23 -0
  43. data/lib/jade/frontend/forward_declaration/error/duplicate_constructor_name.rb +45 -0
  44. data/lib/jade/frontend/forward_declaration/error/duplicate_type_name.rb +42 -0
  45. data/lib/jade/frontend/forward_declaration/error.rb +3 -0
  46. data/lib/jade/frontend/forward_declaration/helper.rb +58 -0
  47. data/lib/jade/frontend/forward_declaration/import_declaration.rb +4 -0
  48. data/lib/jade/frontend/forward_declaration/interface_declaration.rb +6 -0
  49. data/lib/jade/frontend/forward_declaration/module.rb +4 -0
  50. data/lib/jade/frontend/forward_declaration/struct_declaration.rb +10 -1
  51. data/lib/jade/frontend/forward_declaration/type_alias_declaration.rb +37 -0
  52. data/lib/jade/frontend/forward_declaration/type_declaration.rb +10 -1
  53. data/lib/jade/frontend/forward_declaration.rb +2 -0
  54. data/lib/jade/frontend/pattern_analysis/exhaustiveness.rb +47 -43
  55. data/lib/jade/frontend/pattern_analysis/matrix.rb +28 -219
  56. data/lib/jade/frontend/pattern_analysis/pattern.rb +132 -0
  57. data/lib/jade/frontend/pattern_analysis/redundancy.rb +24 -0
  58. data/lib/jade/frontend/pattern_analysis/signature.rb +220 -0
  59. data/lib/jade/frontend/pattern_analysis/usefulness.rb +72 -0
  60. data/lib/jade/frontend/pattern_analysis/witnesses.rb +73 -0
  61. data/lib/jade/frontend/pattern_analysis.rb +5 -38
  62. data/lib/jade/frontend/semantic_analysis/body.rb +29 -1
  63. data/lib/jade/frontend/semantic_analysis/error/duplicate_implementation.rb +39 -0
  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 +4 -0
  69. data/lib/jade/frontend/semantic_analysis/helper.rb +41 -0
  70. data/lib/jade/frontend/semantic_analysis/implementation.rb +13 -10
  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/deriving.rb +5 -4
  84. data/lib/jade/frontend/type_checking/constraints.rb +18 -3
  85. data/lib/jade/frontend/type_checking/error/display.rb +49 -0
  86. data/lib/jade/frontend/type_checking/error/division_by_zero.rb +31 -0
  87. data/lib/jade/frontend/type_checking/error/empty_range_pattern.rb +25 -0
  88. data/lib/jade/frontend/type_checking/error/function_body_type_mismatch.rb +4 -3
  89. data/lib/jade/frontend/type_checking/error/function_call_type_mismatch.rb +108 -5
  90. data/lib/jade/frontend/type_checking/error/if_branch_type_mismatch.rb +1 -1
  91. data/lib/jade/frontend/type_checking/error/if_branches_type_mismatch.rb +1 -1
  92. data/lib/jade/frontend/type_checking/error/if_condition_type_mismatch.rb +1 -1
  93. data/lib/jade/frontend/type_checking/error/implementation_type_mismatch.rb +1 -1
  94. data/lib/jade/frontend/type_checking/error/list_item_type_mismatch.rb +1 -1
  95. data/lib/jade/frontend/type_checking/error/missing_patterns.rb +20 -4
  96. data/lib/jade/frontend/type_checking/error/pattern_type_mismatch.rb +7 -1
  97. data/lib/jade/frontend/type_checking/error/range_pattern_type.rb +29 -0
  98. data/lib/jade/frontend/type_checking/error/record_access_type_mismatch.rb +2 -2
  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 +6 -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 +47 -2
  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/type_alias_declaration.rb +18 -0
  110. data/lib/jade/frontend/type_checking/inference.rb +2 -0
  111. data/lib/jade/frontend/type_checking/loader.rb +11 -0
  112. data/lib/jade/frontend/type_checking/port_resolution.rb +26 -8
  113. data/lib/jade/frontend/type_checking/substitution.rb +2 -2
  114. data/lib/jade/frontend/type_checking.rb +3 -1
  115. data/lib/jade/frontend/usage_analysis.rb +5 -1
  116. data/lib/jade/interop/boundary.rb +64 -24
  117. data/lib/jade/interop/error.rb +35 -7
  118. data/lib/jade/interop/lowering.rb +8 -2
  119. data/lib/jade/lsp/converters.rb +83 -2
  120. data/lib/jade/lsp/handlers.rb +122 -25
  121. data/lib/jade/lsp/snippets.rb +12 -0
  122. data/lib/jade/lsp/state.rb +11 -4
  123. data/lib/jade/module_loader/build.rb +36 -0
  124. data/lib/jade/module_loader.rb +1 -0
  125. data/lib/jade/parsing/combinators.rb +78 -2
  126. data/lib/jade/parsing/error.rb +43 -6
  127. data/lib/jade/parsing/type.rb +5 -3
  128. data/lib/jade/parsing.rb +140 -26
  129. data/lib/jade/runtime.rb +32 -13
  130. data/lib/jade/source.rb +6 -2
  131. data/lib/jade/stdlib/basics.rb +22 -3
  132. data/lib/jade/stdlib/char.rb +2 -0
  133. data/lib/jade/stdlib/clock.rb +6 -2
  134. data/lib/jade/stdlib/decimal.rb +51 -14
  135. data/lib/jade/stdlib/intrinsics.rb +59 -6
  136. data/lib/jade/stdlib/list.rb +37 -1
  137. data/lib/jade/stdlib/number.rb +47 -0
  138. data/lib/jade/stdlib/range.rb +211 -0
  139. data/lib/jade/stdlib.rb +11 -4
  140. data/lib/jade/symbol/alias.rb +11 -0
  141. data/lib/jade/symbol/parser.rb +1 -1
  142. data/lib/jade/symbol.rb +13 -1
  143. data/lib/jade/task.rb +74 -56
  144. data/lib/jade/type/anonymous_record.rb +11 -2
  145. data/lib/jade/type/application.rb +11 -2
  146. data/lib/jade/type/base.rb +26 -0
  147. data/lib/jade/type/function.rb +11 -2
  148. data/lib/jade/type.rb +36 -10
  149. data/lib/jade/version.rb +1 -1
  150. data/lib/jade.rb +1 -0
  151. metadata +35 -3
  152. data/lib/jade/frontend/type_checking/constraints/deriving/assignable.rb +0 -170
@@ -0,0 +1,220 @@
1
+ module Jade
2
+ module Frontend
3
+ module PatternAnalysis
4
+ # One shape a column's first pattern can take. `arg_types` are the
5
+ # columns it opens up when a row is specialized on it.
6
+ ConstructorCase = Data.define(:name, :arg_types) do
7
+ def arity
8
+ arg_types.size
9
+ end
10
+
11
+ def matches?(pattern)
12
+ pattern in Constructor(constructor: ^(name))
13
+ end
14
+
15
+ def witness(args)
16
+ Constructor[name, args]
17
+ end
18
+ end
19
+
20
+ # A literal the rows mention. Types whose values are literals have no
21
+ # enumerable set of these, so one is only ever split on because some row
22
+ # asked for it by name.
23
+ ValueCase = Data.define(:value) do
24
+ def name
25
+ value
26
+ end
27
+
28
+ def arity
29
+ 0
30
+ end
31
+
32
+ def arg_types
33
+ []
34
+ end
35
+
36
+ def matches?(pattern)
37
+ pattern in Literal(value: ^(value))
38
+ end
39
+
40
+ def witness(_args)
41
+ Literal[value]
42
+ end
43
+ end
44
+
45
+ # A stretch of the integer line no pattern in the column divides any
46
+ # further, so it is either wholly matched or wholly missed.
47
+ IntervalCase = Data.define(:from, :to) do
48
+ def arity
49
+ 0
50
+ end
51
+
52
+ def arg_types
53
+ []
54
+ end
55
+
56
+ def matches?(pattern)
57
+ case pattern
58
+ in Interval then pattern.covers?(self)
59
+ in Literal(value: Integer => n) then n == from && n == to
60
+ else false
61
+ end
62
+ end
63
+
64
+ def witness(_args)
65
+ Interval[from, to]
66
+ end
67
+ end
68
+
69
+ # The cases a column of a given type can be split into, and whether they
70
+ # account for every value of that type. When they don't, a row headed by
71
+ # a wildcard covers ground no case names, so the column cannot be
72
+ # decided by splitting alone.
73
+ Split = Data.define(:cases, :total) do
74
+ def covering(head)
75
+ cases.select { it.matches?(head) }
76
+ end
77
+
78
+ def touched_by?(heads)
79
+ cases.any? { |kase| heads.any? { kase.matches?(it) } }
80
+ end
81
+
82
+ def covered_by?(heads)
83
+ total && cases.any? &&
84
+ cases.all? { |kase| heads.any? { kase.matches?(it) } }
85
+ end
86
+ end
87
+
88
+ module Signature
89
+ extend self
90
+
91
+ UNIT = Split[[], true]
92
+ OPEN = Split[[], false]
93
+
94
+ def of(type, heads, env)
95
+ case type
96
+ in Type::Application(constructor: Type::Constructor(name:), args:)
97
+ named(type, name, args, heads, env)
98
+
99
+ else
100
+ OPEN
101
+ end
102
+ end
103
+
104
+ # No value of this type exists, so no row over it can be reached and
105
+ # nothing about it can be missing.
106
+ def uninhabited?(type)
107
+ name_of(type) == 'Basics.Never'
108
+ end
109
+
110
+ # A single-shape type is destructured into its fields rather than split
111
+ # into cases, so the columns behind it stay checkable.
112
+ def expandable?(type, env)
113
+ return true if type in Type::AnonymousRecord
114
+
115
+ env.lookup_def(name_of(type)).is_a?(TypeChecking::StructDef)
116
+ end
117
+
118
+ def fields(type, env)
119
+ case type
120
+ in Type::AnonymousRecord(fields:) then fields
121
+ else env.lookup_def(name_of(type)).body.fields
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ def name_of(type)
128
+ case type
129
+ in Type::Application(constructor: Type::Constructor(name:)) then name
130
+ else nil
131
+ end
132
+ end
133
+
134
+ def named(type, name, args, heads, env)
135
+ case name
136
+ in 'Basics.Bool'
137
+ Split[
138
+ [ConstructorCase['Basics.True', []], ConstructorCase['Basics.False', []]],
139
+ true,
140
+ ]
141
+
142
+ in /^Tuple\.Tuple[2-4]$/
143
+ Split[[ConstructorCase[name, args]], true]
144
+
145
+ in 'List.List'
146
+ args => [element]
147
+
148
+ Split[
149
+ [
150
+ ConstructorCase['List.Nil', []],
151
+ ConstructorCase['List.Cons', [element, type]],
152
+ ],
153
+ true,
154
+ ]
155
+
156
+ in 'Basics.Int'
157
+ integers(heads)
158
+
159
+ in 'Basics.Float' | 'String.String'
160
+ OPEN
161
+
162
+ else
163
+ union(type, name, env)
164
+ end
165
+ end
166
+
167
+ # Every bound in the column cuts the line in two, and the pieces
168
+ # between consecutive cuts are the coarsest split that still lets each
169
+ # pattern be answered whole. The pieces partition Int, so the result is
170
+ # total even though Int cannot be enumerated: a stretch no pattern
171
+ # names comes back as a gap rather than as an open column.
172
+ def integers(heads)
173
+ cuts = heads.flat_map { cuts_of(it) }.compact.uniq.sort
174
+
175
+ return OPEN if cuts.empty?
176
+
177
+ [nil, *cuts]
178
+ .zip([*cuts.map { it - 1 }, nil])
179
+ .map { |(from, to)| IntervalCase[from, to] }
180
+ .then { Split[it, true] }
181
+ end
182
+
183
+ def cuts_of(head)
184
+ case head
185
+ in Interval(from: Integer | nil => from, to: Integer | nil => to)
186
+ [from, to && to + 1]
187
+ in Literal(value: Integer => n) then [n, n + 1]
188
+ else []
189
+ end
190
+ end
191
+
192
+ def union(type, name, env)
193
+ type_def = env.lookup_def(name)
194
+
195
+ # A type the user cannot destructure from here — reached through an
196
+ # imported struct's field, or an opaque intrinsic. Any pattern they
197
+ # can write against it is trivially exhaustive.
198
+ return UNIT if type_def.nil? || type_def.opaque?
199
+ return OPEN unless type_def.is_a?(TypeChecking::TypeDef)
200
+
201
+ type_def
202
+ .constructors
203
+ .map { ConstructorCase[it.name, instantiate(it.args, type_def, type)] }
204
+ .then { Split[it, true] }
205
+ end
206
+
207
+ def instantiate(args, type_def, type)
208
+ args.map do |arg|
209
+ next arg unless arg.is_a?(Type::Var)
210
+
211
+ type_def
212
+ .type_params
213
+ .find_index { arg.name == it.name }
214
+ .then { type.args.at(it) }
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,72 @@
1
+ module Jade
2
+ module Frontend
3
+ module PatternAnalysis
4
+ # Maranget's other half: can any value reach `row` that the rows above it
5
+ # did not already take? A branch that is not useful is dead code.
6
+ module Usefulness
7
+ extend self
8
+
9
+ def useful?(matrix, row, env)
10
+ return matrix.empty? if matrix.types.empty?
11
+
12
+ type = matrix.types.first
13
+
14
+ return false if Signature.uninhabited?(type)
15
+ return expanded(matrix, row, env) if Signature.expandable?(type, env)
16
+
17
+ # The row under test cuts the column too, so its own bounds have to
18
+ # be in the split or it will not line up with the cases.
19
+ split = Signature.of(type, matrix.heads + [row.first], env)
20
+
21
+ case row.first
22
+ in Wildcard
23
+ open_column(matrix, row, split, env)
24
+
25
+ in Interval
26
+ split.covering(row.first).any? { narrowed(matrix, row, it, env) }
27
+
28
+ in Literal(value:)
29
+ split
30
+ .covering(row.first)
31
+ .then { it.empty? ? [ValueCase[value]] : it }
32
+ .any? { narrowed(matrix, row, it, env) }
33
+
34
+ # Deadness is a proof. A constructor this column's type does not have
35
+ # yields none, so the branch stands.
36
+ in Constructor
37
+ split
38
+ .covering(row.first)
39
+ .then { it.empty? || it.any? { narrowed(matrix, row, it, env) } }
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ # A wildcard reaches whatever the rows above left open. When they name
46
+ # every case of the type there is no such ground, and the question
47
+ # moves inside the cases instead.
48
+ def open_column(matrix, row, split, env)
49
+ if split.covered_by?(matrix.heads)
50
+ split.cases.any? { narrowed(matrix, row, it, env) }
51
+ else
52
+ useful?(matrix.default, row.drop(1), env)
53
+ end
54
+ end
55
+
56
+ def narrowed(matrix, row, kase, env)
57
+ head = row.first
58
+ args =
59
+ kase.matches?(head) ? head.args : Array.new(kase.arity) { Wildcard[] }
60
+
61
+ useful?(matrix.specialize(kase), args + row.drop(1), env)
62
+ end
63
+
64
+ def expanded(matrix, row, env)
65
+ fields = Signature.fields(matrix.types.first, env)
66
+
67
+ useful?(matrix.expand(env), matrix.expand_row(row, fields), env)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,73 @@
1
+ module Jade
2
+ module Frontend
3
+ module PatternAnalysis
4
+ # Maranget's witness generation: the patterns a match would need to add
5
+ # to become exhaustive. An empty result means it already is.
6
+ module Witnesses
7
+ extend self
8
+
9
+ def missing(matrix, env)
10
+ return exhausted_columns(matrix) if matrix.types.empty?
11
+
12
+ type = matrix.types.first
13
+
14
+ return Matrix.empty if Signature.uninhabited?(type)
15
+ return Matrix.wildcard(matrix.types) if matrix.empty?
16
+ return expanded(matrix, env) if Signature.expandable?(type, env)
17
+
18
+ heads = matrix.heads
19
+ split = Signature.of(type, heads, env)
20
+
21
+ return unmatched_column(matrix, env) unless split.touched_by?(heads)
22
+
23
+ split
24
+ .cases
25
+ .reduce(Matrix.empty(matrix.types)) do |acc, kase|
26
+ witnesses(matrix, kase, env, heads.any? { kase.matches?(it) })
27
+ .then { acc.concat(it) }
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def expanded(matrix, env)
34
+ missing(matrix.expand(env), env)
35
+ end
36
+
37
+ def exhausted_columns(matrix)
38
+ matrix.empty? ? Matrix[[[]], []] : Matrix.empty
39
+ end
40
+
41
+ # Nothing in this column narrows anything — a type with no cases to
42
+ # enumerate, or one every row left open. Whatever is missing lives in
43
+ # the columns after it.
44
+ def unmatched_column(matrix, env)
45
+ missing(matrix.default, env)
46
+ .map { [Wildcard[]] + it }
47
+ .with(types: matrix.types)
48
+ end
49
+
50
+ # A case the first column matches is explored by specializing on it.
51
+ # One it never matches can only be missing as a whole, so its witnesses
52
+ # come from the rows that would have covered it — the ones headed by a
53
+ # wildcard. Recursing there instead of into the case is what keeps a
54
+ # recursive type from expanding forever.
55
+ def witnesses(matrix, kase, env, matched)
56
+ return Matrix.empty if kase.arg_types.any? { Signature.uninhabited?(it) }
57
+
58
+ found =
59
+ if matched
60
+ missing(matrix.specialize(kase), env)
61
+ .map { [kase.witness(it.take(kase.arity))] + it.drop(kase.arity) }
62
+
63
+ else
64
+ missing(matrix.default, env)
65
+ .map { [kase.witness(Array.new(kase.arity) { Wildcard[] })] + it }
66
+ end
67
+
68
+ found.with(types: matrix.types)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -1,40 +1,7 @@
1
+ require 'jade/frontend/pattern_analysis/pattern'
2
+ require 'jade/frontend/pattern_analysis/signature'
1
3
  require 'jade/frontend/pattern_analysis/matrix'
4
+ require 'jade/frontend/pattern_analysis/witnesses'
5
+ require 'jade/frontend/pattern_analysis/usefulness'
2
6
  require 'jade/frontend/pattern_analysis/exhaustiveness'
3
-
4
- module Jade
5
- module Frontend
6
- module PatternAnalysis
7
- Wildcard = Data.define do
8
- def wildcard?; true; end
9
- def to_s; '_'; end
10
- end
11
-
12
- Literal = Data.define(:value, :type) do
13
- def wildcard?; false; end
14
- def to_s; value.inspect; end
15
- end
16
-
17
- Constructor = Data.define(:constructor, :args) do
18
- def wildcard?; false; end
19
-
20
- def to_s
21
- if constructor.start_with?('Tuple.')
22
- "(#{args.map(&:to_s).join(', ')})"
23
- else
24
- name = constructor.split('.').last
25
- args.empty? ? name : "#{name}(#{args.map(&:to_s).join(', ')})"
26
- end
27
- end
28
- end
29
-
30
- Record = Data.define(:fields) do
31
- def wildcard?; false; end
32
-
33
- def to_s
34
- fields_str = fields.map { |k, v| "#{k}: #{v}" }.join(', ')
35
- "{ #{fields_str} }"
36
- end
37
- end
38
- end
39
- end
40
- end
7
+ require 'jade/frontend/pattern_analysis/redundancy'
@@ -24,9 +24,37 @@ module Jade
24
24
  end
25
25
 
26
26
  analyze_in_sequence(expressions, registry, scope, entry)
27
- .add_errors(duplicate_errors)
27
+ .add_errors(duplicate_errors + duplicate_implementation_errors(expressions, entry))
28
28
  .map_node { node.with(expressions: it) }
29
29
  end
30
+
31
+ private
32
+
33
+ def duplicate_implementation_errors(expressions, entry)
34
+ expressions
35
+ .select { it.is_a?(AST::Implementation) }
36
+ .group_by { implementation_key(it, entry) }
37
+ .reject { |key, impls| key.nil? || impls.size < 2 }
38
+ .flat_map do |(interface, type), (first, *rest)|
39
+ rest.map do |dup|
40
+ Error::DuplicateImplementation.new(
41
+ entry.name,
42
+ dup.range,
43
+ interface:,
44
+ type:,
45
+ first_span: first.range,
46
+ parameterized: dup.applied_type.args.any?,
47
+ )
48
+ end
49
+ end
50
+ end
51
+
52
+ def implementation_key(node, entry)
53
+ interface = entry.lookup_type(node.interface)
54
+ type = lookup_applied_type(node.applied_type, entry)
55
+
56
+ [interface.qname, type.qname] if interface && type
57
+ end
30
58
  end
31
59
  end
32
60
  end
@@ -0,0 +1,39 @@
1
+ module Jade
2
+ module Frontend
3
+ module SemanticAnalysis
4
+ module Error
5
+ class DuplicateImplementation < Jade::Error
6
+ def initialize(entry, span, interface:, type:, first_span:, parameterized:)
7
+ @interface = interface
8
+ @type = type
9
+ @first_span = first_span
10
+ @parameterized = parameterized
11
+ super(entry:, span:)
12
+ end
13
+
14
+ def message
15
+ "Duplicate implementation of #{@interface} for #{@type}"
16
+ end
17
+
18
+ def label
19
+ "already implemented"
20
+ end
21
+
22
+ def secondary
23
+ [[@first_span, 'first implemented here']]
24
+ end
25
+
26
+ def notes
27
+ return [] unless @parameterized
28
+
29
+ [Jade::Diagnostics::Annotation[
30
+ :note,
31
+ "an implementation is chosen by the head type `#{@type}` alone — " \
32
+ 'its type arguments do not select between implementations',
33
+ ]]
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,24 @@
1
+ module Jade
2
+ module Frontend
3
+ module SemanticAnalysis
4
+ module Error
5
+ class ImplementationOnAlias < Jade::Error
6
+ def initialize(entry, span, interface:, alias_name:)
7
+ super(entry:, span:)
8
+ @interface = interface
9
+ @alias_name = alias_name
10
+ end
11
+
12
+ def message
13
+ "Cannot implement `#{@interface}` for type alias `#{@alias_name}`; " \
14
+ "an alias is its body, so use a `struct` or a single-variant `type`"
15
+ end
16
+
17
+ def label
18
+ "no impls on type aliases"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,45 @@
1
+ module Jade
2
+ module Frontend
3
+ module SemanticAnalysis
4
+ module Error
5
+ class NoBaseCase < Jade::Error
6
+ HELP = {
7
+ 'struct' => 'wrap the field in something that has an empty case: ' \
8
+ '`Maybe(%<name>s)` can be `Nothing`, `List(%<name>s)` can be empty',
9
+ 'type' => 'add a variant that does not mention `%<name>s`, ' \
10
+ 'the way `Nothing` does for `Maybe`',
11
+ }.freeze
12
+
13
+ def initialize(entry, span, name:, kind:)
14
+ @name = name
15
+ @kind = kind
16
+ super(entry:, span:)
17
+ end
18
+
19
+ def message
20
+ "`#{@name}` can never be built: every `#{@name}` needs a `#{@name}` " \
21
+ 'inside it first'
22
+ end
23
+
24
+ def label
25
+ 'nothing here stops the recursion'
26
+ end
27
+
28
+ def notes
29
+ [
30
+ Jade::Diagnostics::Annotation[
31
+ :note,
32
+ "a type may refer to itself; `#{@name}` just has no case that " \
33
+ "stops, so there is no smallest `#{@name}` to start from",
34
+ ],
35
+ Jade::Diagnostics::Annotation[
36
+ :help,
37
+ format(HELP.fetch(@kind), name: @name),
38
+ ],
39
+ ]
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -2,18 +2,30 @@ module Jade
2
2
  module Frontend
3
3
  module SemanticAnalysis
4
4
  module Error
5
+ # `Rect(w: _, h: 2)` where `Rect` is `Rect(Int, Int)`: positional, so
6
+ # `w:` names nothing. A variant carrying a record takes the hole.
5
7
  class PlaceholderNotAllowed < Jade::Error
6
- def initialize(entry, span, field:)
8
+ def initialize(entry, span, field:, name:)
7
9
  @field = field
10
+ @name = name
8
11
  super(entry:, span:)
9
12
  end
10
13
 
11
14
  def message
12
- "`#{@field}: _` is only allowed when constructing a struct"
15
+ "`#{@name}` is a union variant; its arguments are positional " \
16
+ "and have no names, so `#{@field}:` has nothing to refer to"
13
17
  end
14
18
 
15
19
  def label
16
- "not allowed here"
20
+ 'not allowed here'
21
+ end
22
+
23
+ def notes
24
+ [Jade::Diagnostics::Annotation[
25
+ :help,
26
+ "write `#{@name}(_, x)` positionally, or pipe into the " \
27
+ "first argument with `|> #{@name}(x)`",
28
+ ]]
17
29
  end
18
30
  end
19
31
  end
@@ -0,0 +1,24 @@
1
+ module Jade
2
+ module Frontend
3
+ module SemanticAnalysis
4
+ module Error
5
+ class RecursiveTypeAlias < Jade::Error
6
+ def initialize(entry, span, name:, cycle:)
7
+ super(entry:, span:)
8
+ @name = name
9
+ @cycle = cycle
10
+ end
11
+
12
+ def message
13
+ "Type alias `#{@name}` is recursive (cycle: #{@cycle.join(' -> ')}); " \
14
+ "an alias must be finite, so use a `type` for a recursive shape"
15
+ end
16
+
17
+ def label
18
+ "recursive type alias"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,5 +1,6 @@
1
1
  require 'jade/error'
2
2
 
3
+ require 'jade/frontend/semantic_analysis/error/no_base_case'
3
4
  require 'jade/frontend/semantic_analysis/error/circular_extends'
4
5
  require 'jade/frontend/semantic_analysis/error/constant_not_callable'
5
6
  require 'jade/frontend/semantic_analysis/error/constructor_not_found'
@@ -7,7 +8,9 @@ require 'jade/frontend/semantic_analysis/error/constructor_pattern_arity_mismatc
7
8
  require 'jade/frontend/semantic_analysis/error/duplicate_field'
8
9
  require 'jade/frontend/semantic_analysis/error/placeholder_not_allowed'
9
10
  require 'jade/frontend/semantic_analysis/error/duplicate_function_declaration'
11
+ require 'jade/frontend/semantic_analysis/error/duplicate_implementation'
10
12
  require 'jade/frontend/semantic_analysis/error/duplicate_record_field'
13
+ require 'jade/frontend/semantic_analysis/error/implementation_on_alias'
11
14
  require 'jade/frontend/semantic_analysis/error/invalid_list_rest_pattern'
12
15
  require 'jade/frontend/semantic_analysis/error/kwargs_on_non_constructor'
13
16
  require 'jade/frontend/semantic_analysis/error/missing_exposing_clause'
@@ -20,6 +23,7 @@ require 'jade/frontend/semantic_analysis/error/non_task_port'
20
23
  require 'jade/frontend/semantic_analysis/error/orphan_implementation'
21
24
  require 'jade/frontend/semantic_analysis/error/predicate_must_return_bool'
22
25
  require 'jade/frontend/semantic_analysis/error/predicate_name_not_allowed'
26
+ require 'jade/frontend/semantic_analysis/error/recursive_type_alias'
23
27
  require 'jade/frontend/semantic_analysis/error/shadowing_error'
24
28
  require 'jade/frontend/semantic_analysis/error/type_args_mismatch'
25
29
  require 'jade/frontend/semantic_analysis/error/type_param_required'