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
@@ -49,6 +49,24 @@ 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
+
60
+ private def lookup_applied_type(applied_type, entry)
61
+ case applied_type.constructor
62
+ in AST::TypeName(type:)
63
+ entry.lookup_type(type)
64
+ in AST::QualifiedTypeName(path:)
65
+ *module_parts, type_name = path
66
+ entry.lookup_qualified_type(module_parts.join('.'), type_name)
67
+ end
68
+ end
69
+
52
70
  def analyze_duplicate_fields(fields, entry)
53
71
  fields
54
72
  .group_by(&:key)
@@ -87,9 +105,28 @@ module Jade
87
105
 
88
106
  in Symbol::RecordType(row_var:)
89
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) : []
90
113
  end
91
114
  end
92
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
+
93
130
  def validate_type_symbol(symbol, registry, entry)
94
131
  case symbol
95
132
  in Symbol::Union(variants:, type_params:)
@@ -144,6 +181,10 @@ module Jade
144
181
  in Symbol::Struct(type_params:, record_type:)
145
182
  validate_type_symbol(record_type, registry, entry) +
146
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) }
147
188
  end
148
189
  end
149
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
@@ -73,16 +86,6 @@ module Jade
73
86
 
74
87
  private
75
88
 
76
- def lookup_applied_type(applied_type, entry)
77
- case applied_type.constructor
78
- in AST::TypeName(type:)
79
- entry.lookup_type(type)
80
- in AST::QualifiedTypeName(path:)
81
- *module_parts, type_name = path
82
- entry.lookup_qualified_type(module_parts.join('.'), type_name)
83
- end
84
- end
85
-
86
89
  def local_type_name(applied_type)
87
90
  case applied_type.constructor
88
91
  in AST::TypeName(type:) then type
@@ -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
@@ -0,0 +1,95 @@
1
+ module Jade
2
+ module Frontend
3
+ module SemanticAnalysis
4
+ module TypeAliasDeclaration
5
+ # An alias is replaced by its body, so one that reaches itself would
6
+ # expand forever. This finds the path back, or nothing.
7
+ module CycleDetection
8
+ extend self
9
+
10
+ def call(symbol_ref, registry, entry)
11
+ symbol = registry.lookup(symbol_ref)
12
+ return [] unless symbol.body
13
+
14
+ qname = symbol.qualified_name
15
+ cycle = find(qname, symbol.body, registry, [qname], ::Set[])
16
+
17
+ return [] unless cycle
18
+ return [] unless reports_for?(qname, cycle, registry)
19
+
20
+ [
21
+ Error::RecursiveTypeAlias.new(
22
+ entry.name,
23
+ symbol.decl_span,
24
+ name: symbol_ref.name,
25
+ cycle: cycle.map { Symbol.unqualified_name(it) },
26
+ ),
27
+ ]
28
+ end
29
+
30
+ private
31
+
32
+ # Every alias in a cycle finds it, so `A = B, B = A` would be
33
+ # reported twice for one mistake. The first by declaration reports
34
+ # and the rest stay quiet, which needs no state shared between
35
+ # declarations since each walk already knows the whole cycle.
36
+ def reports_for?(qname, cycle, registry)
37
+ qname == cycle.min_by { declaration_order(it, registry) }
38
+ end
39
+
40
+ def declaration_order(qname, registry)
41
+ Symbol
42
+ .type_ref_from_qualified_name(qname)
43
+ .then { registry.lookup(it) }
44
+ .then { [it.decl_span&.begin || 0, qname] }
45
+ end
46
+
47
+ # Returns the qname-path back to `start_qname` if found, nil
48
+ # otherwise. Whether an alias reaches `start_qname` does not depend
49
+ # on the path taken to it, so `explored` is shared across the whole
50
+ # walk and an alias that came up clean once is never walked again.
51
+ # Without it an alias naming another one twice doubles the work per
52
+ # level.
53
+ def find(start_qname, sym, registry, visited, explored)
54
+ case sym
55
+ in Symbol::TypeRef
56
+ resolved = registry.lookup(sym)
57
+ return nil unless resolved.is_a?(Symbol::Alias)
58
+
59
+ qname = resolved.qualified_name
60
+ return visited + [qname] if qname == start_qname
61
+ return nil unless explored.add?(qname)
62
+
63
+ resolved.body &&
64
+ find(start_qname, resolved.body, registry, visited + [qname], explored)
65
+
66
+ in Symbol::TypeApplication(constructor:, args:)
67
+ first(start_qname, [constructor, *args], registry, visited, explored)
68
+
69
+ in Symbol::PartialApplication(constructor:, args:)
70
+ first(start_qname, [constructor, *args], registry, visited, explored)
71
+
72
+ in Symbol::FunctionType(params:, return_type:)
73
+ first(start_qname, params + [return_type], registry, visited, explored)
74
+
75
+ in Symbol::RecordType(fields:)
76
+ first(start_qname, fields.values, registry, visited, explored)
77
+
78
+ # Leaves (Variable, primitives, etc.) — can't transitively
79
+ # contain an alias reference, so no cycle through them.
80
+ else
81
+ nil
82
+ end
83
+ end
84
+
85
+ def first(start_qname, syms, registry, visited, explored)
86
+ syms
87
+ .lazy
88
+ .filter_map { find(start_qname, it, registry, visited, explored) }
89
+ .first
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,48 @@
1
+ require 'jade/frontend/semantic_analysis/type_alias_declaration/cycle_detection'
2
+
3
+ module Jade
4
+ module Frontend
5
+ module SemanticAnalysis
6
+ module TypeAliasDeclaration
7
+ extend self
8
+ extend Helper
9
+
10
+ def analyze(node, registry, scope, entry)
11
+ node => AST::TypeAliasDeclaration(name:)
12
+
13
+ symbol_ref = entry.lookup_type(name).to_ref
14
+
15
+ Result
16
+ .init(node.with(symbol: symbol_ref), scope)
17
+ .add_errors(validate_no_unbound_vars(symbol_ref, registry, entry))
18
+ .add_errors(validate_type_symbol(symbol_ref, registry, entry))
19
+ .add_errors(CycleDetection.call(symbol_ref, registry, entry))
20
+ end
21
+
22
+ private
23
+
24
+ def validate_no_unbound_vars(symbol_ref, registry, entry)
25
+ actual_symbol = registry.lookup(symbol_ref)
26
+ return [] unless actual_symbol.body
27
+
28
+ missing = collect_vars(actual_symbol.body, registry)
29
+ .group_by(&:name)
30
+ .reject { |name, _| actual_symbol.type_params.map(&:name).include?(name) }
31
+ .values
32
+ .flatten
33
+
34
+ return [] if missing.empty?
35
+
36
+ [
37
+ Error::UnboundTypeVariable.new(
38
+ entry.name,
39
+ missing.size == 1 ? missing.first.decl_span : actual_symbol.decl_span,
40
+ type_name: symbol_ref.name,
41
+ variables: missing.map(&:name).uniq,
42
+ ),
43
+ ]
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -15,6 +15,7 @@ module Jade
15
15
  .map_node { it.with(symbol: symbol_ref) }
16
16
  .add_errors(validate_no_unbound_vars(symbol_ref, registry, entry))
17
17
  .add_errors(validate_type_symbol(symbol_ref, registry, entry))
18
+ .add_errors(validate_inhabited(symbol_ref, registry, entry, kind: 'type'))
18
19
  end
19
20
 
20
21
  private
@@ -1,5 +1,6 @@
1
1
  require 'jade/frontend/semantic_analysis/error'
2
2
 
3
+ require 'jade/frontend/semantic_analysis/inhabitedness'
3
4
  require 'jade/frontend/semantic_analysis/helper'
4
5
  require 'jade/frontend/semantic_analysis/module_node'
5
6
  require 'jade/frontend/semantic_analysis/import_declaration'
@@ -19,6 +20,7 @@ require 'jade/frontend/semantic_analysis/interop_import_declaration'
19
20
  require 'jade/frontend/semantic_analysis/implementation'
20
21
  require 'jade/frontend/semantic_analysis/implementation_function'
21
22
  require 'jade/frontend/semantic_analysis/struct_declaration'
23
+ require 'jade/frontend/semantic_analysis/type_alias_declaration'
22
24
  require 'jade/frontend/semantic_analysis/interface_declaration'
23
25
  require 'jade/frontend/semantic_analysis/if_then_else'
24
26
  require 'jade/frontend/semantic_analysis/member_access'
@@ -28,6 +30,7 @@ require 'jade/frontend/semantic_analysis/case_of'
28
30
  require 'jade/frontend/semantic_analysis/case_of_branch'
29
31
  require 'jade/frontend/semantic_analysis/pattern_wildcard'
30
32
  require 'jade/frontend/semantic_analysis/pattern_literal'
33
+ require 'jade/frontend/semantic_analysis/pattern_range'
31
34
  require 'jade/frontend/semantic_analysis/pattern_binding'
32
35
  require 'jade/frontend/semantic_analysis/pattern_constructor'
33
36
  require 'jade/frontend/semantic_analysis/pattern_record'
@@ -86,6 +89,7 @@ module Jade
86
89
  in AST::Implementation then Implementation
87
90
  in AST::ImplementationFunction then ImplementationFunction
88
91
  in AST::StructDeclaration then StructDeclaration
92
+ in AST::TypeAliasDeclaration then TypeAliasDeclaration
89
93
  in AST::InterfaceDeclaration then InterfaceDeclaration
90
94
  in AST::IfThenElse then IfThenElse
91
95
  in AST::MemberAccess then MemberAccess
@@ -95,6 +99,7 @@ module Jade
95
99
  in AST::CaseOfBranch then CaseOfBranch
96
100
  in AST::Pattern::Wildcard then PatternWildcard
97
101
  in AST::Pattern::Literal then PatternLiteral
102
+ in AST::Pattern::Range then PatternRange
98
103
  in AST::Pattern::Binding then PatternBinding
99
104
  in AST::Pattern::Constructor then PatternConstructor
100
105
  in AST::Pattern::Record then PatternRecord
@@ -64,6 +64,7 @@ module Jade
64
64
  AST::InteropImportDeclaration |
65
65
  AST::TypeDeclaration |
66
66
  AST::StructDeclaration |
67
+ AST::TypeAliasDeclaration |
67
68
  AST::InterfaceDeclaration |
68
69
  AST::Placeholder
69
70
  nil
@@ -0,0 +1,90 @@
1
+ require 'set'
2
+
3
+ module Jade
4
+ module Frontend
5
+ module TypeChecking
6
+ # One mistake is met by every node above it, each reporting it in
7
+ # whatever type the one below left behind. Only the first is news.
8
+ module Cascade
9
+ extend self
10
+
11
+ def prune(errors)
12
+ errors
13
+ .then { one_view_per_span(it) }
14
+ .then { drop_enclosing(it) }
15
+ .then { drop_downstream(it) }
16
+ end
17
+
18
+ private
19
+
20
+ # Parent and child, reporting one unification failure. A subclass
21
+ # of TypeMismatch exists to say more, so it is the one to keep.
22
+ def one_view_per_span(errors)
23
+ errors
24
+ .chunk_while { |a, b| a.span && a.span == b.span }
25
+ .flat_map { |group| mismatches?(group) ? [most_specific(group)] : group }
26
+ end
27
+
28
+ def mismatches?(group)
29
+ group.size > 1 && group.all? { it.is_a?(Error::TypeMismatch) }
30
+ end
31
+
32
+ def most_specific(group)
33
+ group
34
+ .each_with_index
35
+ .min_by { |error, i| [-depth(error.class), i] }
36
+ .first
37
+ end
38
+
39
+ def depth(klass)
40
+ klass.ancestors.take_while { it != Jade::Error }.size
41
+ end
42
+
43
+ # A mismatch spanning a mismatch already reported is the node above
44
+ # it complaining about what the one below produced.
45
+ def drop_enclosing(errors)
46
+ errors.reject do |error|
47
+ error.is_a?(Error::TypeMismatch) &&
48
+ errors.any? { encloses?(error, it) }
49
+ end
50
+ end
51
+
52
+ def encloses?(outer, inner)
53
+ return false unless inner.is_a?(Error::TypeMismatch)
54
+ return false if outer.equal?(inner) || outer.span.nil? || inner.span.nil?
55
+
56
+ outer.span.begin <= inner.span.begin &&
57
+ inner.span.end <= outer.span.end &&
58
+ outer.span != inner.span
59
+ end
60
+
61
+ # That variable is the hole the first failure left, so anything
62
+ # naming it fails for the same reason.
63
+ def drop_downstream(errors)
64
+ errors
65
+ .reduce([[], ::Set.new]) do |(kept, seen), error|
66
+ vars = inference_vars(error)
67
+
68
+ next [kept, seen] if vars.any? { seen.include?(it) }
69
+
70
+ [kept + [error], seen | vars]
71
+ end
72
+ .first
73
+ end
74
+
75
+ # Two errors mentioning the author's `a` are not thereby the same
76
+ # error; only inference's own variables mark a shared failure.
77
+ def inference_vars(error)
78
+ return ::Set.new unless error.is_a?(Error::TypeMismatch)
79
+
80
+ [error.expected, error.actual]
81
+ .compact
82
+ .flat_map(&:unbound_vars)
83
+ .select { it.name.nil? }
84
+ .map(&:id)
85
+ .to_set
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -47,6 +47,9 @@ module Jade
47
47
  failed(constraint, entry_name)
48
48
  end
49
49
 
50
+ in Type::AnonymousRecord(fields:)
51
+ derive_anonymous_record(constraint, fields, lookup, entry_name)
52
+
50
53
  else
51
54
  failed(constraint, entry_name)
52
55
  end
@@ -93,7 +96,14 @@ module Jade
93
96
 
94
97
  def derive_struct(constraint, struct_sym, type_args, registry, lookup, entry_name)
95
98
  fields = struct_fields(struct_sym, type_args, registry)
99
+ derive_record(constraint, fields, lookup, entry_name)
100
+ end
101
+
102
+ def derive_anonymous_record(constraint, fields, lookup, entry_name)
103
+ derive_record(constraint, fields.to_a, lookup, entry_name)
104
+ end
96
105
 
106
+ def derive_record(constraint, fields, lookup, entry_name)
97
107
  field_deps = fields
98
108
  .map { |_, field_type| Type.constraint(INTERFACE, field_type, nil) }
99
109
 
@@ -2,7 +2,6 @@ require_relative './deriving/helpers.rb'
2
2
  require_relative './deriving/eq.rb'
3
3
  require_relative './deriving/decodable.rb'
4
4
  require_relative './deriving/encodable.rb'
5
- require_relative './deriving/assignable.rb'
6
5
  require_relative './deriving/show.rb'
7
6
 
8
7
  module Jade
@@ -12,14 +11,16 @@ module Jade
12
11
  module Deriving
13
12
  extend self
14
13
 
15
- DERIVERS = [Eq, Show, Decodable, Encodable, Assignable]
14
+ BUILTIN = [Eq, Show, Decodable, Encodable].freeze
15
+
16
+ def derivers = BUILTIN + Extensions.derivers
16
17
 
17
18
  def derivable?(interface)
18
- DERIVERS.any? { it.supports?(interface) }
19
+ derivers.any? { it.supports?(interface) }
19
20
  end
20
21
 
21
22
  def derive(constraint, registry, entry_name, &lookup)
22
- DERIVERS
23
+ derivers
23
24
  .find { it.supports?(constraint.interface) }
24
25
  .then { it.derive(constraint, registry, entry_name, &lookup) }
25
26
  end