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.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +400 -12
  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/formatter/calls.rb +68 -11
  31. data/lib/jade/formatter/collections.rb +4 -4
  32. data/lib/jade/formatter/declarations.rb +13 -15
  33. data/lib/jade/formatter/helper.rb +20 -0
  34. data/lib/jade/formatter/infix_application.rb +19 -4
  35. data/lib/jade/formatter/lambda.rb +24 -12
  36. data/lib/jade/formatter/pattern.rb +3 -0
  37. data/lib/jade/frontend/desugaring/placeholder.rb +40 -17
  38. data/lib/jade/frontend/desugaring.rb +29 -9
  39. data/lib/jade/frontend/fixity_fixer.rb +6 -4
  40. data/lib/jade/frontend/forward_declaration/error/alias_expansion.rb +23 -0
  41. data/lib/jade/frontend/forward_declaration/error/duplicate_constructor_name.rb +45 -0
  42. data/lib/jade/frontend/forward_declaration/error/duplicate_type_name.rb +42 -0
  43. data/lib/jade/frontend/forward_declaration/error.rb +3 -0
  44. data/lib/jade/frontend/forward_declaration/helper.rb +58 -0
  45. data/lib/jade/frontend/forward_declaration/import_declaration.rb +4 -0
  46. data/lib/jade/frontend/forward_declaration/interface_declaration.rb +6 -0
  47. data/lib/jade/frontend/forward_declaration/module.rb +4 -0
  48. data/lib/jade/frontend/forward_declaration/struct_declaration.rb +10 -1
  49. data/lib/jade/frontend/forward_declaration/type_alias_declaration.rb +37 -0
  50. data/lib/jade/frontend/forward_declaration/type_declaration.rb +10 -1
  51. data/lib/jade/frontend/forward_declaration.rb +2 -0
  52. data/lib/jade/frontend/pattern_analysis/exhaustiveness.rb +47 -43
  53. data/lib/jade/frontend/pattern_analysis/matrix.rb +28 -219
  54. data/lib/jade/frontend/pattern_analysis/pattern.rb +132 -0
  55. data/lib/jade/frontend/pattern_analysis/redundancy.rb +24 -0
  56. data/lib/jade/frontend/pattern_analysis/signature.rb +220 -0
  57. data/lib/jade/frontend/pattern_analysis/usefulness.rb +72 -0
  58. data/lib/jade/frontend/pattern_analysis/witnesses.rb +73 -0
  59. data/lib/jade/frontend/pattern_analysis.rb +5 -38
  60. data/lib/jade/frontend/semantic_analysis/error/implementation_on_alias.rb +24 -0
  61. data/lib/jade/frontend/semantic_analysis/error/no_base_case.rb +45 -0
  62. data/lib/jade/frontend/semantic_analysis/error/placeholder_not_allowed.rb +15 -3
  63. data/lib/jade/frontend/semantic_analysis/error/recursive_type_alias.rb +24 -0
  64. data/lib/jade/frontend/semantic_analysis/error.rb +3 -0
  65. data/lib/jade/frontend/semantic_analysis/helper.rb +31 -0
  66. data/lib/jade/frontend/semantic_analysis/implementation.rb +13 -0
  67. data/lib/jade/frontend/semantic_analysis/inhabitedness.rb +90 -0
  68. data/lib/jade/frontend/semantic_analysis/interop_import_declaration.rb +28 -16
  69. data/lib/jade/frontend/semantic_analysis/keyed_call.rb +21 -5
  70. data/lib/jade/frontend/semantic_analysis/pattern_range.rb +16 -0
  71. data/lib/jade/frontend/semantic_analysis/struct_declaration.rb +1 -0
  72. data/lib/jade/frontend/semantic_analysis/type_alias_declaration/cycle_detection.rb +95 -0
  73. data/lib/jade/frontend/semantic_analysis/type_alias_declaration.rb +48 -0
  74. data/lib/jade/frontend/semantic_analysis/type_declaration.rb +1 -0
  75. data/lib/jade/frontend/semantic_analysis.rb +5 -0
  76. data/lib/jade/frontend/type_checking/canonicalize.rb +1 -0
  77. data/lib/jade/frontend/type_checking/cascade.rb +90 -0
  78. data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +10 -0
  79. data/lib/jade/frontend/type_checking/constraints.rb +18 -3
  80. data/lib/jade/frontend/type_checking/error/display.rb +49 -0
  81. data/lib/jade/frontend/type_checking/error/division_by_zero.rb +31 -0
  82. data/lib/jade/frontend/type_checking/error/empty_range_pattern.rb +25 -0
  83. data/lib/jade/frontend/type_checking/error/function_body_type_mismatch.rb +4 -3
  84. data/lib/jade/frontend/type_checking/error/function_call_type_mismatch.rb +108 -5
  85. data/lib/jade/frontend/type_checking/error/if_branch_type_mismatch.rb +1 -1
  86. data/lib/jade/frontend/type_checking/error/if_branches_type_mismatch.rb +1 -1
  87. data/lib/jade/frontend/type_checking/error/if_condition_type_mismatch.rb +1 -1
  88. data/lib/jade/frontend/type_checking/error/implementation_type_mismatch.rb +1 -1
  89. data/lib/jade/frontend/type_checking/error/list_item_type_mismatch.rb +1 -1
  90. data/lib/jade/frontend/type_checking/error/missing_patterns.rb +20 -4
  91. data/lib/jade/frontend/type_checking/error/pattern_type_mismatch.rb +7 -1
  92. data/lib/jade/frontend/type_checking/error/range_pattern_type.rb +29 -0
  93. data/lib/jade/frontend/type_checking/error/record_access_type_mismatch.rb +2 -2
  94. data/lib/jade/frontend/type_checking/error/recursive_derivation.rb +25 -0
  95. data/lib/jade/frontend/type_checking/error/type_mismatch.rb +10 -2
  96. data/lib/jade/frontend/type_checking/error/unreachable_branch.rb +22 -0
  97. data/lib/jade/frontend/type_checking/error.rb +6 -0
  98. data/lib/jade/frontend/type_checking/inference/assign.rb +1 -1
  99. data/lib/jade/frontend/type_checking/inference/case_of.rb +21 -4
  100. data/lib/jade/frontend/type_checking/inference/division.rb +50 -0
  101. data/lib/jade/frontend/type_checking/inference/function_call.rb +26 -1
  102. data/lib/jade/frontend/type_checking/inference/lambda.rb +1 -1
  103. data/lib/jade/frontend/type_checking/inference/pattern.rb +35 -0
  104. data/lib/jade/frontend/type_checking/inference/type_alias_declaration.rb +18 -0
  105. data/lib/jade/frontend/type_checking/inference.rb +2 -0
  106. data/lib/jade/frontend/type_checking/loader.rb +11 -0
  107. data/lib/jade/frontend/type_checking/port_resolution.rb +26 -8
  108. data/lib/jade/frontend/type_checking/substitution.rb +2 -2
  109. data/lib/jade/frontend/type_checking.rb +3 -1
  110. data/lib/jade/frontend/usage_analysis.rb +5 -1
  111. data/lib/jade/interop/boundary.rb +64 -24
  112. data/lib/jade/interop/error.rb +35 -7
  113. data/lib/jade/interop/lowering.rb +8 -2
  114. data/lib/jade/lsp/converters.rb +83 -2
  115. data/lib/jade/lsp/handlers.rb +122 -25
  116. data/lib/jade/lsp/snippets.rb +12 -0
  117. data/lib/jade/lsp/state.rb +11 -4
  118. data/lib/jade/module_loader/build.rb +36 -0
  119. data/lib/jade/module_loader.rb +1 -0
  120. data/lib/jade/parsing/combinators.rb +78 -2
  121. data/lib/jade/parsing/error.rb +43 -6
  122. data/lib/jade/parsing/type.rb +5 -3
  123. data/lib/jade/parsing.rb +140 -26
  124. data/lib/jade/runtime.rb +32 -13
  125. data/lib/jade/source.rb +6 -2
  126. data/lib/jade/stdlib/basics.rb +22 -3
  127. data/lib/jade/stdlib/char.rb +2 -0
  128. data/lib/jade/stdlib/clock.rb +6 -2
  129. data/lib/jade/stdlib/decimal.rb +51 -14
  130. data/lib/jade/stdlib/intrinsics.rb +59 -6
  131. data/lib/jade/stdlib/list.rb +37 -1
  132. data/lib/jade/stdlib/number.rb +47 -0
  133. data/lib/jade/stdlib/range.rb +211 -0
  134. data/lib/jade/stdlib.rb +11 -4
  135. data/lib/jade/symbol/alias.rb +11 -0
  136. data/lib/jade/symbol/parser.rb +1 -1
  137. data/lib/jade/symbol.rb +13 -1
  138. data/lib/jade/task.rb +0 -2
  139. data/lib/jade/type/anonymous_record.rb +11 -2
  140. data/lib/jade/type/application.rb +11 -2
  141. data/lib/jade/type/base.rb +26 -0
  142. data/lib/jade/type/function.rb +11 -2
  143. data/lib/jade/type.rb +36 -10
  144. data/lib/jade/version.rb +1 -1
  145. metadata +33 -2
@@ -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'
@@ -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'
@@ -9,6 +10,7 @@ require 'jade/frontend/semantic_analysis/error/placeholder_not_allowed'
9
10
  require 'jade/frontend/semantic_analysis/error/duplicate_function_declaration'
10
11
  require 'jade/frontend/semantic_analysis/error/duplicate_implementation'
11
12
  require 'jade/frontend/semantic_analysis/error/duplicate_record_field'
13
+ require 'jade/frontend/semantic_analysis/error/implementation_on_alias'
12
14
  require 'jade/frontend/semantic_analysis/error/invalid_list_rest_pattern'
13
15
  require 'jade/frontend/semantic_analysis/error/kwargs_on_non_constructor'
14
16
  require 'jade/frontend/semantic_analysis/error/missing_exposing_clause'
@@ -21,6 +23,7 @@ require 'jade/frontend/semantic_analysis/error/non_task_port'
21
23
  require 'jade/frontend/semantic_analysis/error/orphan_implementation'
22
24
  require 'jade/frontend/semantic_analysis/error/predicate_must_return_bool'
23
25
  require 'jade/frontend/semantic_analysis/error/predicate_name_not_allowed'
26
+ require 'jade/frontend/semantic_analysis/error/recursive_type_alias'
24
27
  require 'jade/frontend/semantic_analysis/error/shadowing_error'
25
28
  require 'jade/frontend/semantic_analysis/error/type_args_mismatch'
26
29
  require 'jade/frontend/semantic_analysis/error/type_param_required'
@@ -49,6 +49,14 @@ module Jade
49
49
  Result[results.map(&:node), results.flat_map(&:errors), scope]
50
50
  end
51
51
 
52
+ # A local alias is a `Symbol::Alias`; one that crossed a module arrives
53
+ # as a `TypeRef`, so both spellings have to be resolved before asking.
54
+ def resolved_alias(symbol, registry)
55
+ symbol
56
+ .then { it.is_a?(Symbol::TypeRef) ? registry.lookup(it) : it }
57
+ .then { it if it.is_a?(Symbol::Alias) }
58
+ end
59
+
52
60
  private def lookup_applied_type(applied_type, entry)
53
61
  case applied_type.constructor
54
62
  in AST::TypeName(type:)
@@ -97,9 +105,28 @@ module Jade
97
105
 
98
106
  in Symbol::RecordType(row_var:)
99
107
  row_var.nil? ? [] : [row_var]
108
+
109
+ in Symbol::Alias(body:)
110
+ # Aliases are transparent — vars are whatever the body has.
111
+ # Skip if body unresolved (cycle / pre-deep-decl).
112
+ body ? collect_vars(body, registry) : []
100
113
  end
101
114
  end
102
115
 
116
+ def validate_inhabited(symbol_ref, registry, entry, kind:)
117
+ symbol = registry.lookup(symbol_ref)
118
+ return [] if Inhabitedness.inhabited?(symbol, registry)
119
+
120
+ [
121
+ Error::NoBaseCase.new(
122
+ entry.name,
123
+ symbol.decl_span,
124
+ name: symbol_ref.name,
125
+ kind:,
126
+ ),
127
+ ]
128
+ end
129
+
103
130
  def validate_type_symbol(symbol, registry, entry)
104
131
  case symbol
105
132
  in Symbol::Union(variants:, type_params:)
@@ -154,6 +181,10 @@ module Jade
154
181
  in Symbol::Struct(type_params:, record_type:)
155
182
  validate_type_symbol(record_type, registry, entry) +
156
183
  type_params.flat_map { validate_type_symbol(it, registry, entry) }
184
+
185
+ in Symbol::Alias(type_params:, body:)
186
+ (body ? validate_type_symbol(body, registry, entry) : []) +
187
+ type_params.flat_map { validate_type_symbol(it, registry, entry) }
157
188
  end
158
189
  end
159
190
  end
@@ -20,6 +20,19 @@ module Jade
20
20
  functions_r = analyze_in_parallel(functions, registry, scope, entry)
21
21
 
22
22
  make_error = ->(klass, **kw) { klass.new(entry.name, node.range, **kw) }
23
+
24
+ resolved_alias(type_sym, registry).then do |alias_sym|
25
+ if alias_sym
26
+ return make_error
27
+ .(
28
+ Error::ImplementationOnAlias,
29
+ interface: entry.lookup_type(interface).qname,
30
+ alias_name: alias_sym.qname,
31
+ )
32
+ .then { Result[node, [it], scope] }
33
+ end
34
+ end
35
+
23
36
  type_is_local = entry.defined_types.key?(local_type_name(applied_type))
24
37
 
25
38
  unless entry.defined_types.key?(interface) || type_is_local
@@ -0,0 +1,90 @@
1
+ module Jade
2
+ module Frontend
3
+ module SemanticAnalysis
4
+ # Least fixed point over "can a value of this type be built at all".
5
+ # A type currently being asked about counts as uninhabited, so a shape
6
+ # that needs itself to exist bottoms out at false rather than looping.
7
+ module Inhabitedness
8
+ extend self
9
+
10
+ DECLARATIONS = [Symbol::Union, Symbol::Struct, Symbol::Alias].freeze
11
+
12
+ def inhabited?(symbol, registry)
13
+ reachable?(symbol, registry, ::Set[], {})
14
+ end
15
+
16
+ private
17
+
18
+ def reachable?(sym, registry, visiting, subst)
19
+ case sym
20
+ in Symbol::Variable(name:)
21
+ subst.fetch(name, true)
22
+
23
+ in Symbol::RecordType(fields:)
24
+ fields.values.all? { reachable?(it, registry, visiting, subst) }
25
+
26
+ in Symbol::TypeRef
27
+ declaration(registry.lookup(sym), [], registry, visiting)
28
+
29
+ in Symbol::TypeApplication(constructor:, args:)
30
+ args
31
+ .map { reachable?(it, registry, visiting, subst) }
32
+ .then { declaration(resolve(constructor, registry), it, registry, visiting) }
33
+
34
+ in Symbol::Union | Symbol::Struct | Symbol::Alias
35
+ declaration(sym, [], registry, visiting)
36
+
37
+ else
38
+ true
39
+ end
40
+ end
41
+
42
+ def resolve(constructor, registry)
43
+ constructor.is_a?(Symbol::TypeRef) ? registry.lookup(constructor) : constructor
44
+ end
45
+
46
+ def declaration(head, arg_values, registry, visiting)
47
+ return true unless DECLARATIONS.any? { head.is_a?(it) }
48
+
49
+ # A declaration asked about on its own has no arguments to speak of,
50
+ # and a parameter stands for whatever a caller supplies, so it
51
+ # counts as something that exists.
52
+ values = head.type_params.each_index.map { arg_values.fetch(it, true) }
53
+ key = [head.qualified_name, values]
54
+ return false if visiting.include?(key)
55
+
56
+ seen = visiting + [key]
57
+ subst = head.type_params.map(&:name).zip(values).to_h
58
+
59
+ case head
60
+ in Symbol::Alias(body:)
61
+ body.nil? || reachable?(body, registry, seen, subst)
62
+
63
+ in Symbol::Struct(record_type:)
64
+ record_type.nil? || reachable?(record_type, registry, seen, subst)
65
+
66
+ in Symbol::Union(variants:) if tuple?(head)
67
+ values.all?
68
+
69
+ in Symbol::Union(variants:)
70
+ variants.empty? || variants.any? { buildable?(it, registry, seen, subst) }
71
+ end
72
+ end
73
+
74
+ # A tuple carries no variants to read, and unlike the other natives
75
+ # written that way it is a product: there is no empty one to fall back
76
+ # on the way a `List` has `[]`.
77
+ def tuple?(head)
78
+ head.qualified_name.start_with?('Tuple.Tuple')
79
+ end
80
+
81
+ def buildable?(variant_ref, registry, visiting, subst)
82
+ registry
83
+ .lookup(variant_ref)
84
+ .args
85
+ .all? { reachable?(it, registry, visiting, subst) }
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -18,7 +18,7 @@ module Jade
18
18
  .flat_map { validate_type_symbol(it.symbol, registry, entry) }
19
19
 
20
20
  task_errors = functions_with_symbols
21
- .flat_map { |fn| task_shape_errors(fn, entry) }
21
+ .flat_map { |fn| task_shape_errors(fn, registry, entry) }
22
22
 
23
23
  Result
24
24
  .init(node.with(functions: functions_with_symbols), scope)
@@ -27,38 +27,50 @@ module Jade
27
27
 
28
28
  private
29
29
 
30
- def task_shape_errors(fn, entry)
31
- unless task_type?(fn.symbol.return_type)
30
+ TASK = 'Task.Task'
31
+
32
+ # Asked of the expanded type rather than the written symbol: an alias
33
+ # is its body, and both questions here are about the shape.
34
+ def task_shape_errors(fn, registry, entry)
35
+ return_type = expand(fn.symbol.return_type, registry)
36
+
37
+ unless task_type?(return_type)
32
38
  Error::NonTaskPort
33
39
  .new(entry.name, fn.range, fn_name: fn.name)
34
40
  .then { return [it] }
35
41
  end
36
42
 
37
- fn.symbol.return_type => Symbol::TypeApplication(args: [ok_arm, err_arm])
43
+ return_type => Type::Application(args: [ok_arm, err_arm])
38
44
 
39
45
  [ok_arm, err_arm]
40
46
  .any? { contains_task?(it) }
41
47
  .then { it ? [Error::NestedTaskPort.new(entry.name, fn.range, fn_name: fn.name)] : [] }
42
48
  end
43
49
 
44
- def task_type?(symbol)
45
- case symbol
46
- in Symbol::TypeApplication(constructor: Symbol::TypeRef['Task', 'Task'])
47
- true
50
+ def expand(symbol, registry)
51
+ Type
52
+ .from_symbol(symbol, registry, TypeChecking::VarGen.new)
53
+ .first
54
+ end
48
55
 
49
- else
50
- false
51
- end
56
+ def task_type?(type)
57
+ type in Type::Application(constructor: Type::Constructor(name: TASK))
52
58
  end
53
59
 
54
- def contains_task?(symbol)
55
- case symbol
56
- in Symbol::TypeApplication(constructor: Symbol::TypeRef['Task', 'Task'])
57
- true
60
+ def contains_task?(type)
61
+ case type
62
+ in Type::Application(constructor: Type::Constructor(name: TASK))
63
+ true
58
64
 
59
- in Symbol::TypeApplication(args:)
65
+ in Type::Application(args:)
60
66
  args.any? { contains_task?(it) }
61
67
 
68
+ in Type::Function(args:, return_type:)
69
+ (args + [return_type]).any? { contains_task?(it) }
70
+
71
+ in Type::AnonymousRecord(fields:)
72
+ fields.values.any? { contains_task?(it) }
73
+
62
74
  else
63
75
  false
64
76
  end
@@ -44,8 +44,10 @@ module Jade
44
44
  # still bare AST and `Placeholder.lift` can turn the call into a
45
45
  # lambda — the same route a positional partial application takes.
46
46
  def partially_applied(node, callee, fields, parent, constructor, registry, scope, entry, callee_r)
47
- errors = Validation.errors(node, fields, parent, constructor, registry, entry) +
48
- placeholder_errors(fields, parent, entry)
47
+ # A positional variant has no field names, so every key is also
48
+ # an unknown field. The placeholder error is the one that says why.
49
+ errors = placeholder_errors(fields, parent, constructor, entry)
50
+ .then { it.any? ? it : Validation.errors(node, fields, parent, constructor, registry, entry) }
49
51
 
50
52
  return Result[node, callee_r.errors + errors, scope] if errors.any?
51
53
 
@@ -81,12 +83,26 @@ module Jade
81
83
  end
82
84
  end
83
85
 
84
- def placeholder_errors(fields, parent, entry)
85
- return [] if parent.is_a?(Symbol::Struct)
86
+ # Validation already reports a keyed call on a plain function. A
87
+ # keyed variant lowers to a record literal, which takes holes.
88
+ def placeholder_errors(fields, parent, constructor, entry)
89
+ return [] if constructor.nil? || parent.is_a?(Symbol::Struct)
90
+ return [] if keyed_variant?(constructor)
86
91
 
87
92
  fields
88
93
  .select { it.value.is_a?(AST::Placeholder) }
89
- .map { Error::PlaceholderNotAllowed.new(entry.name, it.range, field: it.key) }
94
+ .map do
95
+ Error::PlaceholderNotAllowed.new(
96
+ entry.name,
97
+ it.range,
98
+ field: it.key,
99
+ name: constructor.name,
100
+ )
101
+ end
102
+ end
103
+
104
+ def keyed_variant?(constructor)
105
+ constructor in Symbol::Constructor(args: [Symbol::RecordType])
90
106
  end
91
107
 
92
108
  def lower(node, callee, fields, parent, constructor, registry)
@@ -0,0 +1,16 @@
1
+ module Jade
2
+ module Frontend
3
+ module SemanticAnalysis
4
+ module PatternRange
5
+ extend self
6
+ extend Helper
7
+
8
+ # Both bounds are integer literals, so there is no name to resolve and
9
+ # nothing to bind.
10
+ def analyze(node, _registry, scope, _entry)
11
+ Result.init(node, scope)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -14,6 +14,7 @@ module Jade
14
14
  .init(node.with(symbol: symbol_ref), scope)
15
15
  .add_errors(validate_no_unbound_vars(symbol_ref, registry, entry))
16
16
  .add_errors(validate_type_symbol(symbol_ref, registry, entry))
17
+ .add_errors(validate_inhabited(symbol_ref, registry, entry, kind: 'struct'))
17
18
  end
18
19
 
19
20
  private