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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +112 -0
- data/lib/jade/ast.rb +1 -1
- data/lib/jade/codegen/context.rb +18 -1
- data/lib/jade/codegen/function_call.rb +9 -2
- data/lib/jade/codegen/inlines.rb +2 -0
- data/lib/jade/codegen.rb +7 -5
- data/lib/jade/diagnostics/renderer.rb +5 -1
- data/lib/jade/entry.rb +32 -10
- data/lib/jade/frontend/forward_declaration/error/ambiguous_type.rb +36 -0
- data/lib/jade/frontend/forward_declaration/error.rb +1 -0
- data/lib/jade/frontend/forward_declaration/helper.rb +7 -0
- data/lib/jade/frontend/forward_declaration/import_declaration.rb +10 -2
- data/lib/jade/frontend/semantic_analysis/constructor_reference.rb +5 -0
- data/lib/jade/frontend/semantic_analysis/error/ambiguous_name.rb +36 -0
- data/lib/jade/frontend/semantic_analysis/error/top_level_statement.rb +28 -0
- data/lib/jade/frontend/semantic_analysis/error.rb +2 -0
- data/lib/jade/frontend/semantic_analysis/module_node.rb +21 -1
- data/lib/jade/frontend/semantic_analysis/variable_reference.rb +5 -0
- data/lib/jade/frontend/semantic_analysis.rb +4 -6
- data/lib/jade/frontend/type_checking/constraints/deriving/show.rb +24 -0
- data/lib/jade/frontend/type_checking/error/narrowed_method_variable.rb +36 -0
- data/lib/jade/frontend/type_checking/error/narrowed_signature.rb +53 -0
- data/lib/jade/frontend/type_checking/error.rb +2 -0
- data/lib/jade/frontend/type_checking/inference/assign.rb +7 -4
- data/lib/jade/frontend/type_checking/inference/case_of.rb +1 -6
- data/lib/jade/frontend/type_checking/inference/constructor_reference.rb +1 -1
- data/lib/jade/frontend/type_checking/inference/function_declaration.rb +21 -0
- data/lib/jade/frontend/type_checking/inference/helpers.rb +11 -0
- data/lib/jade/frontend/type_checking/inference/implementation.rb +23 -2
- data/lib/jade/frontend/type_checking/inference/lambda.rb +1 -4
- data/lib/jade/frontend/type_checking/inference/list.rb +16 -2
- data/lib/jade/frontend/type_checking/narrowing.rb +93 -0
- data/lib/jade/frontend/type_checking/pattern_checks.rb +30 -0
- data/lib/jade/frontend/type_checking/state.rb +9 -2
- data/lib/jade/frontend/type_checking.rb +3 -6
- data/lib/jade/frontend.rb +0 -22
- data/lib/jade/parsing/error.rb +15 -0
- data/lib/jade/registry.rb +1 -1
- data/lib/jade/stdlib/compiled.rb +8 -1
- data/lib/jade/stdlib/dict.rb +12 -0
- data/lib/jade/stdlib/intrinsics.rb +8 -1
- data/lib/jade/stdlib/set.rb +12 -0
- data/lib/jade/stdlib.rb +8 -1
- data/lib/jade/symbol/ambiguous.rb +5 -0
- data/lib/jade/symbol.rb +1 -0
- data/lib/jade/version.rb +1 -1
- metadata +10 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1b6d2de0940d8842121c48456180fa3cbc04345d0bf991c31f8e008019c4e017
|
|
4
|
+
data.tar.gz: 425323f1b3c92b1d817b088a3973b86c94f7743fe185197cb5d60c680281184f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
data/lib/jade/codegen/context.rb
CHANGED
|
@@ -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
|
|
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
|
data/lib/jade/codegen/inlines.rb
CHANGED
data/lib/jade/codegen.rb
CHANGED
|
@@ -107,11 +107,13 @@ module Jade
|
|
|
107
107
|
dict_consts = {}
|
|
108
108
|
|
|
109
109
|
outer, inner, wrappers =
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
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.
|
|
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
|
-
.
|
|
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
|