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,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
 
@@ -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