jade-lang 0.11.3 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +112 -0
  3. data/lib/jade/ast.rb +1 -1
  4. data/lib/jade/codegen/context.rb +18 -1
  5. data/lib/jade/codegen/function_call.rb +9 -2
  6. data/lib/jade/codegen/inlines.rb +2 -0
  7. data/lib/jade/codegen.rb +7 -5
  8. data/lib/jade/diagnostics/renderer.rb +5 -1
  9. data/lib/jade/entry.rb +32 -10
  10. data/lib/jade/frontend/forward_declaration/error/ambiguous_type.rb +36 -0
  11. data/lib/jade/frontend/forward_declaration/error.rb +1 -0
  12. data/lib/jade/frontend/forward_declaration/helper.rb +7 -0
  13. data/lib/jade/frontend/forward_declaration/import_declaration.rb +10 -2
  14. data/lib/jade/frontend/semantic_analysis/constructor_reference.rb +5 -0
  15. data/lib/jade/frontend/semantic_analysis/error/ambiguous_name.rb +36 -0
  16. data/lib/jade/frontend/semantic_analysis/error/top_level_statement.rb +28 -0
  17. data/lib/jade/frontend/semantic_analysis/error.rb +2 -0
  18. data/lib/jade/frontend/semantic_analysis/module_node.rb +21 -1
  19. data/lib/jade/frontend/semantic_analysis/variable_reference.rb +5 -0
  20. data/lib/jade/frontend/semantic_analysis.rb +4 -6
  21. data/lib/jade/frontend/type_checking/constraints/deriving/show.rb +24 -0
  22. data/lib/jade/frontend/type_checking/error/narrowed_method_variable.rb +36 -0
  23. data/lib/jade/frontend/type_checking/error/narrowed_signature.rb +53 -0
  24. data/lib/jade/frontend/type_checking/error.rb +2 -0
  25. data/lib/jade/frontend/type_checking/inference/assign.rb +7 -4
  26. data/lib/jade/frontend/type_checking/inference/case_of.rb +1 -6
  27. data/lib/jade/frontend/type_checking/inference/constructor_reference.rb +1 -1
  28. data/lib/jade/frontend/type_checking/inference/function_declaration.rb +21 -0
  29. data/lib/jade/frontend/type_checking/inference/helpers.rb +11 -0
  30. data/lib/jade/frontend/type_checking/inference/implementation.rb +23 -2
  31. data/lib/jade/frontend/type_checking/inference/lambda.rb +1 -4
  32. data/lib/jade/frontend/type_checking/inference/list.rb +16 -2
  33. data/lib/jade/frontend/type_checking/narrowing.rb +93 -0
  34. data/lib/jade/frontend/type_checking/pattern_checks.rb +30 -0
  35. data/lib/jade/frontend/type_checking/state.rb +9 -2
  36. data/lib/jade/frontend/type_checking.rb +3 -6
  37. data/lib/jade/frontend.rb +0 -22
  38. data/lib/jade/parsing/error.rb +15 -0
  39. data/lib/jade/registry.rb +1 -1
  40. data/lib/jade/stdlib/compiled.rb +8 -1
  41. data/lib/jade/stdlib/dict.rb +12 -0
  42. data/lib/jade/stdlib/intrinsics.rb +8 -1
  43. data/lib/jade/stdlib/set.rb +12 -0
  44. data/lib/jade/stdlib.rb +8 -1
  45. data/lib/jade/symbol/ambiguous.rb +5 -0
  46. data/lib/jade/symbol.rb +1 -0
  47. data/lib/jade/version.rb +1 -1
  48. metadata +10 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5529a34cf9b8a3d407d78642bca0281ec0a422dcded4f5875636e85f7018d7e4
4
- data.tar.gz: c8da773021523ebb4d93836d8c8dee0ba0dca6acf4115f32ff46443a8dfbf08e
3
+ metadata.gz: 1b6d2de0940d8842121c48456180fa3cbc04345d0bf991c31f8e008019c4e017
4
+ data.tar.gz: 425323f1b3c92b1d817b088a3973b86c94f7743fe185197cb5d60c680281184f
5
5
  SHA512:
6
- metadata.gz: 26bab9740a9f008e4611a52210ec002f314e1fec4169239cd1a4aaa37eb090829a6fd20f4742ab475c169e4dc856652deddf12eca95f862f413668accdeda724
7
- data.tar.gz: 243a05ffe69948767ccb7900b4e2cf443dccdb50d540f6cd16998863a6917e2742718769b742c0d0bf9f1e121aa4ee51cd85b70084ea7f26d669a1e49bf14d54
6
+ metadata.gz: 0da5c33762fdac3b212474c72925c1d47c48b915f7b4a6877a6328b3c07ac15430e0f97c5647d714886d498c94850a1a448100ae29cfd73d7f97abc3bfc2248c
7
+ data.tar.gz: ee26d40ebaa14f0587675692a83d977f6e7eae0ba80c342929847636c9c5509778fa7e19977974deb2421bf7cab0e17a066cafd4188f121d6165347cd1a4b3b4
data/CHANGELOG.md CHANGED
@@ -4,6 +4,118 @@ All notable changes to this project are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
5
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [Unreleased]
8
+
9
+ ## [0.12.1] - 2026-09-21
10
+
11
+ Both of these make code compile that should have compiled, so they ride a
12
+ patch rather than waiting for a minor.
13
+
14
+ ### Fixed
15
+
16
+ - **A pattern in a lambda is checked against the type the caller gives it.**
17
+ Coverage was decided where the pattern was written, which for a lambda is
18
+ before the call that types its parameter — so the subject was still a
19
+ variable and anything but a binding or a wildcard was reported as
20
+ non-exhaustive. `(a, b) = p` inside a `List.map` is the common shape;
21
+ `Tuple.first` / `Tuple.second` or a one-armed `case` were the workarounds.
22
+ Coverage and redundancy now run once inference has finished.
23
+
24
+ ### Added
25
+
26
+ - **`Show` renders a `Dict` and a `Set`.** Both already had `Eq`, so they
27
+ compared but could not be printed, and anything that shows a value on
28
+ failure — a test assertion, a debug line — stopped compiling at
29
+ `Show cannot be derived for Dict(String, Int)`. Keys, values and elements
30
+ render through their own instances, so a `String` key keeps its quotes and a
31
+ nested list prints as a list.
32
+
33
+ ## [0.12.0] - 2026-09-17
34
+
35
+ ### Breaking
36
+
37
+ Each of these refuses a program that compiled before. All four were
38
+ accepting something unsound, so the refusal is the fix — but a build that
39
+ passed on 0.11.3 may not pass on this one.
40
+
41
+ - **A signature wider than its body was accepted.** `def anything -> a`
42
+ returning `Nothing`, or `def wrap -> Maybe(a)` returning `Just(1)`, type
43
+ checked: the body quietly fixed the declared variable, and every caller then
44
+ saw the narrower type. A declared type variable now has to stay a variable
45
+ through the body. Fixing it to a concrete type, or making two declared
46
+ variables the same, is an error that says which.
47
+
48
+ - **A statement at the top level of a module is an error.** `x = 2` between
49
+ two `def`s type-checked, and a function reading `x` compiled clean, then
50
+ raised `NameError` at runtime: the binding became a local of the Ruby
51
+ `module` body, out of every method's reach. A module's top level holds
52
+ declarations only; a value belongs in a zero-argument `def`.
53
+
54
+ - **A name imported from two modules picked one without a word.**
55
+ `import Alpha exposing (name)` beside `import Beta exposing (name)`
56
+ compiled, and `name` meant whichever import came last. Using it unqualified
57
+ is now an error naming both modules; qualified uses, and a module that never
58
+ uses it, still compile. The same goes for constructors and for a type named
59
+ in a signature. An explicit import still beats one of the stdlib's default
60
+ imports, now by rule rather than by the order imports were processed in.
61
+
62
+ - **An implementation could fix a variable the method leaves to the call
63
+ site.** 0.11.0 let an interface method quantify a variable the interface
64
+ itself does not name, settled where the method is called. Nothing then
65
+ checked that the implementation was actually that general:
66
+
67
+ interface Runnable(c) with
68
+ to_select : Q(c) -> Sel(a) with Selectable(a)
69
+ end
70
+
71
+ implements Runnable(Sel(a)) with
72
+ to_select: (q) -> { q.result } -- Q(Sel(a)) -> Sel(a), not -> Sel(any)
73
+ end
74
+
75
+ Unification bound the method's `a` to the implementation's, and the binding
76
+ reached no call site, so `Q(Sel(Row)) -> Sel(Other)` compiled and returned a
77
+ row of the wrong type. It is refused now, by name, where it is written.
78
+
79
+ ### Fixed
80
+
81
+ - **A file that ends mid-declaration crashed `jade check`.** The parse error
82
+ for input that runs out carries no span, since there is no token to point
83
+ at, and the renderer called `.begin` on it. The diagnostic now points at the
84
+ last character written, where the missing part belongs, and a label with no
85
+ span renders as its message alone.
86
+
87
+ - **A body its signature rejects crashed the compiler.** A list, a bare
88
+ constructor, or a binding in last position, checked against a type it could
89
+ not be, reached `nil.call` in `State#unify` — the crash 0.10.1 fixed for
90
+ record updates, at the call sites that still had no error block.
91
+ `def limit -> Int` returning `[1]` now reads
92
+ `Expected Int but got List(Int)`.
93
+
94
+ - **A dictionary marker kept the var it was attached with.** A constraint
95
+ records its marker while inference is still running, so it holds whatever
96
+ type variable it had at that moment. Unification may bind that variable to
97
+ another one afterwards, and the enclosing function's dict parameter is keyed
98
+ on the second — so the lookup missed and codegen raised `no dict in scope`,
99
+ naming itself as the bug.
100
+
101
+ It took two constraints on one function to see it: with one, the marker's
102
+ var and the parameter's var were usually the same. jade-sql wanted a
103
+ `pluck` — one column of the caller's type out of every row — which needs
104
+ `Decodable(a)` threaded alongside the error widening, and neither constraint
105
+ alone reproduced it.
106
+
107
+ Codegen now carries the entry's substitution while it emits a body, and
108
+ resolves a marker's var through it before giving up.
109
+
110
+ - **`import M exposing (..)` crashed the compiler.** It asked the imported
111
+ module for `exposed`, which does not exist, so the only way to bring a
112
+ module's whole surface in unqualified was a `NoMethodError`. It now
113
+ imports everything the module exposes, constructors included.
114
+
115
+ - **A string literal's range started one byte in.** `Literal` took its range
116
+ from the content token, so a diagnostic on `"ab"` underlined `ab"`, and
117
+ anything editing source by node range landed inside the quotes.
118
+
7
119
  ## [0.11.3] - 2026-09-16
8
120
 
9
121
  ### Added
data/lib/jade/ast.rb CHANGED
@@ -99,7 +99,7 @@ module Jade
99
99
  ->(tokens) do
100
100
  tokens => [open, token, close]
101
101
 
102
- Literal[token.value, token.range.begin...close.range.end]
102
+ Literal[token.value, open.range.begin...close.range.end]
103
103
  end
104
104
  end
105
105
 
@@ -22,6 +22,23 @@ module Jade
22
22
  @dict_env = prev
23
23
  end
24
24
 
25
+ # The substitution of the module being emitted. A dictionary marker is
26
+ # attached mid-inference, so it holds whatever var the constraint had
27
+ # then; unification may bind that var to another, and the dict env is
28
+ # keyed on the second. Module-scoped because every kind of body resolves
29
+ # markers, not just function declarations.
30
+ def substitution
31
+ @substitution
32
+ end
33
+
34
+ def with_substitution(sub)
35
+ prev = @substitution
36
+ @substitution = sub
37
+ yield
38
+ ensure
39
+ @substitution = prev
40
+ end
41
+
25
42
  # Dictionary source => constant name, filled while a module emits.
26
43
  # A dictionary built entirely from concrete implementations depends
27
44
  # on nothing at the call site, so it is built once at load instead
@@ -58,7 +75,7 @@ module Jade
58
75
  @self_var_name = prev
59
76
  end
60
77
 
61
- # False outside a Module so bare expressions (REPL) get the runtime
78
+ # False outside a Module so bare expressions get the runtime
62
79
  # fallback — no constants exist to reference.
63
80
  def hoist_records?
64
81
  @hoist_records
@@ -113,14 +113,21 @@ module Jade
113
113
 
114
114
  def dispatch_value(entry, registry)
115
115
  case entry
116
- in Type::Constraint(interface:, type: Type::Var(id:))
117
- Codegen.dict_env[[interface, id]]
116
+ in Type::Constraint(interface:, type: Type::Var(id:) => var)
117
+ Codegen.dict_env[[interface, id]] || dict_for_bound_var(interface, var)
118
118
 
119
119
  in Symbol::Implementation
120
120
  hoisted(Pretty.hash(generate_impl_dispatch(entry, registry)))
121
121
  end
122
122
  end
123
123
 
124
+ def dict_for_bound_var(interface, var)
125
+ Codegen
126
+ .substitution
127
+ .apply(var)
128
+ .then { it.is_a?(Type::Var) ? Codegen.dict_env[[interface, it.id]] : nil }
129
+ end
130
+
124
131
  # A dictionary built only from concrete implementations is the same
125
132
  # object on every call, so it is worth building once. One that names
126
133
  # a dict parameter is not: that slot is the caller's, and differs per
@@ -178,6 +178,8 @@ module Jade
178
178
  # Bodies that don't fit a single Ruby expression.
179
179
  NO_INLINE = %w[
180
180
  Number.checked
181
+ Dict.show_with
182
+ Set.show_with
181
183
  List.sort_with
182
184
  List.sort_by_with
183
185
  List.filter_map
data/lib/jade/codegen.rb CHANGED
@@ -107,11 +107,13 @@ module Jade
107
107
  dict_consts = {}
108
108
 
109
109
  outer, inner, wrappers =
110
- with_dict_consts(dict_consts) do
111
- with_boundary_cache(boundary_cache) do
112
- with_dispatched_methods(collect_dispatched_methods(body, registry)) do
113
- with_hoisted_records do
114
- partition_module_body(body.expressions, registry, name.count('.'))
110
+ with_substitution(registry.get(name).env.substitution) do
111
+ with_dict_consts(dict_consts) do
112
+ with_boundary_cache(boundary_cache) do
113
+ with_dispatched_methods(collect_dispatched_methods(body, registry)) do
114
+ with_hoisted_records do
115
+ partition_module_body(body.expressions, registry, name.count('.'))
116
+ end
115
117
  end
116
118
  end
117
119
  end
@@ -20,7 +20,7 @@ module Jade::Diagnostics
20
20
  def render(diagnostic)
21
21
  [
22
22
  header(diagnostic),
23
- diagnostic.primary&.then { span_block(it, severity: diagnostic.severity) if it.source },
23
+ diagnostic.primary&.then { primary_block(it, diagnostic.severity) },
24
24
  *diagnostic
25
25
  .secondary
26
26
  .map { span_block(_1, severity: :secondary) },
@@ -41,6 +41,10 @@ module Jade::Diagnostics
41
41
 
42
42
  private
43
43
 
44
+ def primary_block(label, severity)
45
+ span_block(label, severity:) if label.source && label.span
46
+ end
47
+
44
48
  def header(diagnostic)
45
49
  "#{bold}#{color(diagnostic.severity)}#{diagnostic.severity}:#{reset} " \
46
50
  "#{bold}#{diagnostic.message}#{reset}"
data/lib/jade/entry.rb CHANGED
@@ -64,19 +64,19 @@ module Jade
64
64
  end
65
65
 
66
66
  def imported_values
67
- imports
68
- .flat_map(&:unqualified_symbols)
69
- .select { it.is_a?(Symbol::ValueRef) }
70
- .map { [it.name, it] }
71
- .to_h
67
+ unqualified(Symbol::ValueRef)
72
68
  end
73
69
 
74
70
  def imported_types
75
- imports
76
- .flat_map(&:unqualified_symbols)
77
- .select { it.is_a?(Symbol::TypeRef) }
78
- .map { [it.name, it] }
79
- .to_h
71
+ unqualified(Symbol::TypeRef)
72
+ end
73
+
74
+ def ambiguous_values
75
+ ambiguous(Symbol::ValueRef)
76
+ end
77
+
78
+ def ambiguous_types
79
+ ambiguous(Symbol::TypeRef)
80
80
  end
81
81
 
82
82
  def values
@@ -128,6 +128,28 @@ module Jade
128
128
 
129
129
  private
130
130
 
131
+ def unqualified(kind)
132
+ imports
133
+ .partition(&:implicit)
134
+ .map { |group| refs(group, kind).to_h { [it.name, it] } }
135
+ .then { |(implicit, explicit)| implicit.merge(explicit) }
136
+ end
137
+
138
+ def ambiguous(kind)
139
+ imports
140
+ .reject(&:implicit)
141
+ .then { refs(it, kind) }
142
+ .uniq(&:qualified_name)
143
+ .group_by(&:name)
144
+ .select { |_, candidates| candidates.size > 1 }
145
+ end
146
+
147
+ def refs(imports, kind)
148
+ imports
149
+ .flat_map(&:unqualified_symbols)
150
+ .select { it.is_a?(kind) }
151
+ end
152
+
131
153
  def interface_snapshot
132
154
  value_names = exposes.filter_map { it.is_a?(Symbol::ValueRef) ? it.name : nil }.sort
133
155
  type_names = exposes.filter_map { it.is_a?(Symbol::TypeRef) ? it.name : nil }.sort
@@ -0,0 +1,36 @@
1
+ module Jade
2
+ module Frontend
3
+ module ForwardDeclaration
4
+ module Error
5
+ class AmbiguousType < Jade::Error
6
+ def initialize(entry, span, name:, modules:)
7
+ @name = name
8
+ @modules = modules
9
+ super(entry:, span:)
10
+ end
11
+
12
+ def message
13
+ "Type `#{@name}` is imported from both #{sentence(@modules)}"
14
+ end
15
+
16
+ def label
17
+ 'ambiguous'
18
+ end
19
+
20
+ def notes
21
+ [Jade::Diagnostics::Annotation[
22
+ :help,
23
+ "qualify it, as in `#{@modules.first}.#{@name}`, or drop it from all but one import",
24
+ ]]
25
+ end
26
+
27
+ private
28
+
29
+ def sentence(names)
30
+ "#{names[0...-1].join(', ')} and #{names.last}"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -2,6 +2,7 @@ require 'jade/error'
2
2
 
3
3
  require 'jade/frontend/forward_declaration/error/bad_import'
4
4
  require 'jade/frontend/forward_declaration/error/alias_expansion'
5
+ require 'jade/frontend/forward_declaration/error/ambiguous_type'
5
6
  require 'jade/frontend/forward_declaration/error/duplicate_constructor_name'
6
7
  require 'jade/frontend/forward_declaration/error/duplicate_type_name'
7
8
  require 'jade/frontend/forward_declaration/error/exposed_type_not_found'
@@ -123,6 +123,13 @@ module Jade
123
123
  private
124
124
 
125
125
  def require_type(entry, name, span)
126
+ if !entry.defined_types.key?(name) && (candidates = entry.ambiguous_types[name])
127
+ return Err[Error::AmbiguousType.new(
128
+ entry.name, span, name:,
129
+ modules: candidates.map(&:module_name),
130
+ )]
131
+ end
132
+
126
133
  entry.lookup_type(name)
127
134
  &.then { Ok[it] } ||
128
135
  Err[Error::TypeNotFound.new(
@@ -19,7 +19,15 @@ module Jade
19
19
  end
20
20
 
21
21
  exposing_to_symbol(exposing, entry, importing_module)
22
- .map { ImportEntry[module_name, as&.as || module_name, it, importing_module.exposes] }
22
+ .map do
23
+ ImportEntry.new(
24
+ module_name:,
25
+ alias: as&.as || module_name,
26
+ unqualified_symbols: it,
27
+ qualified_symbols: importing_module.exposes,
28
+ implicit: false,
29
+ )
30
+ end
23
31
  .on_err { return Result[entry, it] } => Ok(import_entry)
24
32
 
25
33
  Result[entry.import(import_entry), []]
@@ -34,7 +42,7 @@ module Jade
34
42
  def exposing_to_symbol(exposing, current_entry, importing_module)
35
43
  case exposing
36
44
  in AST::ExposeNone then Ok[[]]
37
- in AST::ExposeAll then Ok[importing_module.exposed]
45
+ in AST::ExposeAll then Ok[importing_module.exposes.to_a]
38
46
  in AST::ExposeList(items:) then handle_exposing_list(items, current_entry, importing_module)
39
47
  end
40
48
  end
@@ -26,6 +26,11 @@ module Jade
26
26
  .add_errors([it])
27
27
  end
28
28
 
29
+ in Symbol::Ambiguous(modules:)
30
+ Result
31
+ .init(node, scope)
32
+ .add_errors([Error::AmbiguousName.new(entry.name, node.range, name:, modules:)])
33
+
29
34
  in symbol
30
35
  Result.init(node.with(symbol: symbol.to_ref), scope)
31
36
  end
@@ -0,0 +1,36 @@
1
+ module Jade
2
+ module Frontend
3
+ module SemanticAnalysis
4
+ module Error
5
+ class AmbiguousName < Jade::Error
6
+ def initialize(entry, span, name:, modules:)
7
+ @name = name
8
+ @modules = modules
9
+ super(entry:, span:)
10
+ end
11
+
12
+ def message
13
+ "`#{@name}` is imported from both #{sentence(@modules)}"
14
+ end
15
+
16
+ def label
17
+ 'ambiguous'
18
+ end
19
+
20
+ def notes
21
+ [Jade::Diagnostics::Annotation[
22
+ :help,
23
+ "qualify it, as in `#{@modules.first}.#{@name}`, or drop it from all but one import",
24
+ ]]
25
+ end
26
+
27
+ private
28
+
29
+ def sentence(names)
30
+ "#{names[0...-1].join(', ')} and #{names.last}"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ module Jade
2
+ module Frontend
3
+ module SemanticAnalysis
4
+ module Error
5
+ class TopLevelStatement < Jade::Error
6
+ def initialize(entry, span)
7
+ super(entry:, span:)
8
+ end
9
+
10
+ def message
11
+ "Only declarations can appear at the top level of a module"
12
+ end
13
+
14
+ def label
15
+ 'not a declaration'
16
+ end
17
+
18
+ def notes
19
+ [Jade::Diagnostics::Annotation[
20
+ :help,
21
+ 'a value belongs in a zero-argument `def`, like `def limit -> Int`',
22
+ ]]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,6 +1,7 @@
1
1
  require 'jade/error'
2
2
 
3
3
  require 'jade/frontend/semantic_analysis/error/no_base_case'
4
+ require 'jade/frontend/semantic_analysis/error/ambiguous_name'
4
5
  require 'jade/frontend/semantic_analysis/error/circular_extends'
5
6
  require 'jade/frontend/semantic_analysis/error/constant_not_callable'
6
7
  require 'jade/frontend/semantic_analysis/error/constructor_not_found'
@@ -25,6 +26,7 @@ require 'jade/frontend/semantic_analysis/error/predicate_must_return_bool'
25
26
  require 'jade/frontend/semantic_analysis/error/predicate_name_not_allowed'
26
27
  require 'jade/frontend/semantic_analysis/error/recursive_type_alias'
27
28
  require 'jade/frontend/semantic_analysis/error/shadowing_error'
29
+ require 'jade/frontend/semantic_analysis/error/top_level_statement'
28
30
  require 'jade/frontend/semantic_analysis/error/type_args_mismatch'
29
31
  require 'jade/frontend/semantic_analysis/error/type_param_required'
30
32
  require 'jade/frontend/semantic_analysis/error/unbound_type_variable'
@@ -5,6 +5,17 @@ module Jade
5
5
  extend self
6
6
  extend Helper
7
7
 
8
+ DECLARATIONS = [
9
+ AST::FunctionDeclaration,
10
+ AST::TypeDeclaration,
11
+ AST::TypeAliasDeclaration,
12
+ AST::StructDeclaration,
13
+ AST::InterfaceDeclaration,
14
+ AST::Implementation,
15
+ AST::ImportDeclaration,
16
+ AST::InteropImportDeclaration,
17
+ ].freeze
18
+
8
19
  def analyze(node, registry, scope, entry)
9
20
  node => AST::Module(body:, exposing:)
10
21
 
@@ -19,7 +30,16 @@ module Jade
19
30
  .combine(node, scope:,
20
31
  body: analyze_node(body, registry, scope, entry),
21
32
  )
22
- .add_errors(exposing_errors)
33
+ .add_errors(exposing_errors + statement_errors(body, entry))
34
+ end
35
+
36
+ private
37
+
38
+ def statement_errors(body, entry)
39
+ body
40
+ .expressions
41
+ .reject { |node| DECLARATIONS.any? { node.is_a?(it) } }
42
+ .map { Error::TopLevelStatement.new(entry.name, it.range) }
23
43
  end
24
44
  end
25
45
  end
@@ -17,6 +17,11 @@ module Jade
17
17
  candidates: scope.bindings.keys,
18
18
  )])
19
19
 
20
+ in Symbol::Ambiguous(modules:)
21
+ Result
22
+ .init(node.with(symbol: Symbol.var(name, node.range)), scope)
23
+ .add_errors([Error::AmbiguousName.new(entry.name, node.range, name:, modules:)])
24
+
20
25
  in symbol
21
26
  Result.init(node.with(symbol:), scope)
22
27
  end
@@ -53,11 +53,6 @@ module Jade
53
53
  .then { it.errors.any? ? Err[it.errors] : Ok[entry.with(ast: it.node)] }
54
54
  end
55
55
 
56
- def analyze_repl(ast, registry, scope, entry)
57
- analyze_node(ast, registry, scope, entry)
58
- .then { it.errors.any? ? Err[it.errors] : Ok[[it.node, it.scope]] }
59
- end
60
-
61
56
  def analyze_entry(entry, registry)
62
57
  analyze(entry, registry)
63
58
  end
@@ -66,7 +61,10 @@ module Jade
66
61
 
67
62
  def initialize_scope(entry)
68
63
  entry
69
- .values
64
+ .ambiguous_values
65
+ .reject { |name, _| entry.defined_values.key?(name) }
66
+ .to_h { |name, refs| [name, Symbol::Ambiguous[name, refs.map(&:module_name)]] }
67
+ .then { entry.values.merge(it) }
70
68
  .reduce(Scope.new) { |acc, (unq_name, sym)| acc.bind(unq_name, sym) }
71
69
  end
72
70
 
@@ -24,6 +24,8 @@ module Jade
24
24
  # reason. Contained because derivation is private — see
25
25
  # vault jade/plans/jade-test-runner.md for the four layers involved.
26
26
  LIST = 'List.List'
27
+ DICT = 'Dict.Dict'
28
+ SET = 'Set.Set'
27
29
 
28
30
  def special_case(constraint, lookup)
29
31
  case constraint.type
@@ -33,6 +35,12 @@ module Jade
33
35
  in Type::Application(constructor: Type::Constructor(name: LIST), args: [inner])
34
36
  list_show(constraint, inner, lookup)
35
37
 
38
+ in Type::Application(constructor: Type::Constructor(name: DICT), args: [key, value])
39
+ container_show(constraint, [key, value], 'Dict.show_with', lookup)
40
+
41
+ in Type::Application(constructor: Type::Constructor(name: SET), args: [inner])
42
+ container_show(constraint, [inner], 'Set.show_with', lookup)
43
+
36
44
  else
37
45
  nil
38
46
  end
@@ -57,6 +65,22 @@ module Jade
57
65
  end
58
66
  end
59
67
 
68
+ # Threads each element's own `show` into the stdlib function that
69
+ # renders the container.
70
+ def container_show(constraint, inners, fn, lookup)
71
+ inners
72
+ .map { lookup.call(Type.constraint(INTERFACE, it, constraint.origin)) }
73
+ .then { Results.sequence(it) }
74
+ .and_then do |deps|
75
+ args = deps.each_index.map { [:impl_arg, it, 'show'] } + [[:var, 'value']]
76
+
77
+ Symbol::DerivedFunction
78
+ .new(params: ['value'], body: [:call, [:stdlib_fn, fn], args])
79
+ .then { Ok[implementation(constraint, { 'show' => it }, deps:)] }
80
+ end
81
+ end
82
+
83
+
60
84
  def constant(text)
61
85
  Symbol::DerivedFunction.new(params: ['value'], body: text)
62
86
  end
@@ -0,0 +1,36 @@
1
+ module Jade
2
+ module Frontend
3
+ module TypeChecking
4
+ module Error
5
+ class NarrowedMethodVariable < Jade::Error
6
+ def initialize(entry, span, interface:, fn_name:, var_name:, bound_to:)
7
+ super(entry:, span:)
8
+ @interface = interface
9
+ @fn_name = fn_name
10
+ @var_name = var_name
11
+ @bound_to = bound_to
12
+ end
13
+
14
+ def message
15
+ "Implementation of #{@interface}.#{@fn_name} fixes `#{@var_name}` to " \
16
+ "#{@bound_to}, which #{@fn_name} leaves to the call site"
17
+ end
18
+
19
+ def label
20
+ "fixes `#{@var_name}`"
21
+ end
22
+
23
+ def notes
24
+ [
25
+ Jade::Diagnostics::Annotation[
26
+ :help,
27
+ "the implementation has to work for every `#{@var_name}` the " \
28
+ 'method is written for, not one of them',
29
+ ],
30
+ ]
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end