jade-lang 0.10.1 → 0.11.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -0
- data/lib/jade/ast.rb +15 -3
- data/lib/jade/codegen/function_call.rb +10 -10
- data/lib/jade/codegen/function_declaration.rb +2 -10
- data/lib/jade/codegen/helpers.rb +42 -0
- data/lib/jade/codegen/implementation.rb +33 -8
- data/lib/jade/formatter/leaves.rb +12 -1
- data/lib/jade/frontend/forward_declaration/interface_declaration.rb +16 -3
- data/lib/jade/frontend/type_checking/error/cross_module_requirement.rb +36 -0
- data/lib/jade/frontend/type_checking/error/implementation_function_constraint.rb +36 -0
- data/lib/jade/frontend/type_checking/error/undeclared_requirement.rb +35 -0
- data/lib/jade/frontend/type_checking/error.rb +3 -0
- data/lib/jade/frontend/type_checking/inference/function_declaration.rb +0 -5
- data/lib/jade/frontend/type_checking/inference/helpers.rb +9 -0
- data/lib/jade/frontend/type_checking/inference/implementation.rb +93 -16
- data/lib/jade/frontend/type_checking/inference/qualified_access.rb +1 -0
- data/lib/jade/frontend/type_checking/inference/variable_reference.rb +1 -0
- data/lib/jade/frontend/type_checking/requirements.rb +115 -0
- data/lib/jade/frontend/type_checking/state.rb +10 -2
- data/lib/jade/frontend/type_checking.rb +9 -3
- data/lib/jade/parsing.rb +14 -1
- data/lib/jade/symbol/interface_function.rb +2 -1
- data/lib/jade/symbol.rb +2 -2
- data/lib/jade/type.rb +12 -3
- data/lib/jade/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b73668e7e5ab3a62df195d2f978ffcba2ef69e3a2e578eb084ab56dccfe0770f
|
|
4
|
+
data.tar.gz: 6d7420d31cf750127e51ad9191ac068231e92b041779ef4927d0e6feea26fd2f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0036c6f868f892ff83938bfbf5eda69531d4a4fa11aa1abd27978efafddc936bb64320acd6a181caa0b2f55cccb70957c9ba80aad833584857a4b51dc1083ed1
|
|
7
|
+
data.tar.gz: 95940e59148590400e15d252ed8bbb4a7a6b67cd3febbc481a8b176fbb373afd2e20069e3a91541af52aafbf510baa6d1c9fb3f5cd2277b76adaff07015a75e8
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,42 @@ 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
|
+
## [0.11.0] - 2026-09-15
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **An interface method may require an interface of another type.** A method
|
|
12
|
+
can mention a type variable the interface itself does not name, and say what
|
|
13
|
+
that variable has to satisfy:
|
|
14
|
+
|
|
15
|
+
interface Fetchable(x) with
|
|
16
|
+
projection : x -> Sel(a) with Selectable(a)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
`a` is settled where `projection` is called, not where the implementation is
|
|
20
|
+
picked, so each call site resolves the requirement against its own types and
|
|
21
|
+
passes that dictionary alongside the one for `x`. Left undeclared, the
|
|
22
|
+
requirement is inferred from the implementations instead. Declared and
|
|
23
|
+
inferred are reconciled rather than one overriding the other: an
|
|
24
|
+
implementation may require what the method declares, and anything beyond it
|
|
25
|
+
is refused by name.
|
|
26
|
+
|
|
27
|
+
Two cases stay refused. A requirement *inferred* on an interface from
|
|
28
|
+
another module, because a declaration is what travels with the interface and
|
|
29
|
+
there is no foreign entry to amend — declare it and it works across modules.
|
|
30
|
+
And a requirement on a variable the method's signature never names, which
|
|
31
|
+
nothing could satisfy.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **A bare reference to a zero-arg interface method resolves through its
|
|
36
|
+
dictionary.** Dictionaries were only ever attached where a method was called
|
|
37
|
+
or passed as an argument, so a function body that was just `selector` — or
|
|
38
|
+
`Module.selector` — compiled to that name as a Ruby method and died on the
|
|
39
|
+
first call. It now reads the enclosing function's dictionary, or is a compile
|
|
40
|
+
error where none is in scope. Code that was silently broken this way may stop
|
|
41
|
+
compiling.
|
|
42
|
+
|
|
7
43
|
## [0.10.1] - 2026-09-11
|
|
8
44
|
|
|
9
45
|
### Added
|
data/lib/jade/ast.rb
CHANGED
|
@@ -77,7 +77,8 @@ module Jade
|
|
|
77
77
|
define(:ImplementationFunction, :name, :fn)
|
|
78
78
|
|
|
79
79
|
define(:InterfaceDeclaration, :name, :type_param, :functions)
|
|
80
|
-
define(:InterfaceFunctionDecl, :name, :type)
|
|
80
|
+
define(:InterfaceFunctionDecl, :name, :type, :constraints)
|
|
81
|
+
define(:InterfaceConstraint, :interface, :type_param)
|
|
81
82
|
|
|
82
83
|
module Pattern
|
|
83
84
|
extend self
|
|
@@ -751,7 +752,7 @@ module Jade
|
|
|
751
752
|
end
|
|
752
753
|
|
|
753
754
|
def interface_function_decl
|
|
754
|
-
->((name, type)) do
|
|
755
|
+
->((name, type, constraints)) do
|
|
755
756
|
canonical_name =
|
|
756
757
|
case name.type
|
|
757
758
|
in :identifier then name.value
|
|
@@ -761,7 +762,18 @@ module Jade
|
|
|
761
762
|
InterfaceFunctionDecl[
|
|
762
763
|
canonical_name,
|
|
763
764
|
type,
|
|
764
|
-
|
|
765
|
+
constraints,
|
|
766
|
+
name.range.begin...(constraints.last&.range&.end || type.range.end),
|
|
767
|
+
]
|
|
768
|
+
end
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
def interface_constraint
|
|
772
|
+
->((name, type_param)) do
|
|
773
|
+
InterfaceConstraint[
|
|
774
|
+
name.value,
|
|
775
|
+
type_param,
|
|
776
|
+
name.range.begin...type_param.range.end,
|
|
765
777
|
]
|
|
766
778
|
end
|
|
767
779
|
end
|
|
@@ -246,15 +246,6 @@ module Jade
|
|
|
246
246
|
end
|
|
247
247
|
end
|
|
248
248
|
|
|
249
|
-
# When a user fn has var-typed constraints, two definitions are emitted:
|
|
250
|
-
# `name` (Ruby-boundary wrapper, no dicts) and `__name__impl__` (takes
|
|
251
|
-
# dicts). Jade-internal calls target the latter.
|
|
252
|
-
def fn_target_name(fn_sym, registry)
|
|
253
|
-
return fn_sym.name if dict_constraints(fn_sym, registry).empty?
|
|
254
|
-
|
|
255
|
-
fn_impl_synthetic_name(fn_sym.name)
|
|
256
|
-
end
|
|
257
|
-
|
|
258
249
|
# Returns the list of dict args to pass after regular args. Only
|
|
259
250
|
# Symbol::Function callees take dict params; other branches dispatch
|
|
260
251
|
# via `dictionaries` directly inside generate_callee. dictionaries are
|
|
@@ -267,6 +258,7 @@ module Jade
|
|
|
267
258
|
else callee.symbol
|
|
268
259
|
end
|
|
269
260
|
|
|
261
|
+
return interface_dict_args(symbol, dictionaries, registry) if symbol.is_a?(Symbol::InterfaceFunction)
|
|
270
262
|
return "" unless symbol.is_a?(Symbol::Function)
|
|
271
263
|
|
|
272
264
|
fn_constraints(symbol, registry)
|
|
@@ -275,6 +267,14 @@ module Jade
|
|
|
275
267
|
.join(', ')
|
|
276
268
|
end
|
|
277
269
|
|
|
270
|
+
def interface_dict_args(_symbol, dictionaries, registry)
|
|
271
|
+
dictionaries
|
|
272
|
+
.drop(1)
|
|
273
|
+
.compact
|
|
274
|
+
.filter_map { dispatch_value(it, registry) }
|
|
275
|
+
.join(', ')
|
|
276
|
+
end
|
|
277
|
+
|
|
278
278
|
# Ruby-block intrinsics (Dict's `Eq k`, etc.) ignore dispatches — the
|
|
279
279
|
# `in String` body of `generate_impl_fn` drops them.
|
|
280
280
|
def dispatch_dict(entry, registry)
|
|
@@ -356,7 +356,7 @@ module Jade
|
|
|
356
356
|
"#{internal(fn.module_name)}.#{fn.name}"
|
|
357
357
|
|
|
358
358
|
in Symbol::Function => fn
|
|
359
|
-
"#{internal(fn.module_name)}.method(:#{fn
|
|
359
|
+
"#{internal(fn.module_name)}.method(:#{fn_target_name(fn, registry)})"
|
|
360
360
|
end
|
|
361
361
|
end
|
|
362
362
|
|
|
@@ -34,10 +34,9 @@ module Jade
|
|
|
34
34
|
|
|
35
35
|
var_cs = dict_constraints(symbol, registry)
|
|
36
36
|
param_names = params.map { generate_node(it, registry) }
|
|
37
|
-
dict_params = var_cs.each_index.map { dict_synthetic_name(it) }
|
|
38
37
|
|
|
39
|
-
body_code =
|
|
40
|
-
|
|
38
|
+
dict_params, body_code =
|
|
39
|
+
with_dict_params(var_cs) { emit_body(body, symbol, param_names, registry) }
|
|
41
40
|
|
|
42
41
|
target = var_cs.empty? ? name : fn_impl_synthetic_name(name)
|
|
43
42
|
sig = (param_names + dict_params).join(', ')
|
|
@@ -200,13 +199,6 @@ module Jade
|
|
|
200
199
|
.then { it.substitution.apply(it.bindings[symbol.qualified_name].type) }
|
|
201
200
|
end
|
|
202
201
|
|
|
203
|
-
def build_dict_env(var_cs)
|
|
204
|
-
var_cs
|
|
205
|
-
.each_with_index
|
|
206
|
-
.reduce({}) do |env, (c, i)|
|
|
207
|
-
env.merge([c.interface, c.type.id] => dict_synthetic_name(i))
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
202
|
end
|
|
211
203
|
end
|
|
212
204
|
end
|
data/lib/jade/codegen/helpers.rb
CHANGED
|
@@ -74,6 +74,48 @@ module Jade
|
|
|
74
74
|
.then { "__#{it}__impl__" }
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
def fn_target_name(fn_sym, registry)
|
|
78
|
+
return fn_sym.name if dict_constraints(fn_sym, registry).empty?
|
|
79
|
+
|
|
80
|
+
fn_impl_synthetic_name(fn_sym.name)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def with_dict_params(constraints)
|
|
84
|
+
constraints
|
|
85
|
+
.each_with_index
|
|
86
|
+
.to_h { |c, i| [[c.interface, c.type.id], dict_synthetic_name(i)] }
|
|
87
|
+
.then { |env| Codegen.with_dict_env(env) { yield } }
|
|
88
|
+
.then { |body| [constraints.each_index.map { dict_synthetic_name(it) }, body] }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def body_markers(node)
|
|
92
|
+
return [] unless node.is_a?(AST::Node)
|
|
93
|
+
|
|
94
|
+
node
|
|
95
|
+
.deconstruct_keys(nil)
|
|
96
|
+
.each_value
|
|
97
|
+
.flat_map { marker_children(it) }
|
|
98
|
+
.flat_map { body_markers(it) }
|
|
99
|
+
.then { own_markers(node) + it }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def own_markers(node)
|
|
103
|
+
return [] unless node.respond_to?(:dictionaries)
|
|
104
|
+
|
|
105
|
+
node
|
|
106
|
+
.dictionaries
|
|
107
|
+
.select { it.is_a?(Type::Constraint) && it.type.is_a?(Type::Var) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def marker_children(value)
|
|
111
|
+
case value
|
|
112
|
+
in Array then value.flat_map { marker_children(it) }
|
|
113
|
+
in Hash then value.each_value.flat_map { marker_children(it) }
|
|
114
|
+
in AST::Node then [value]
|
|
115
|
+
else []
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
77
119
|
def fn_constraints(fn_symbol, registry)
|
|
78
120
|
env = registry.get(fn_symbol.module_name).env
|
|
79
121
|
|
|
@@ -106,7 +106,7 @@ module Jade
|
|
|
106
106
|
end
|
|
107
107
|
|
|
108
108
|
def generate_defs(node, registry)
|
|
109
|
-
node => AST::Implementation(interface:, applied_type:, functions:)
|
|
109
|
+
node => AST::Implementation(interface:, applied_type:, functions:, symbol:)
|
|
110
110
|
|
|
111
111
|
type_name =
|
|
112
112
|
case applied_type.constructor
|
|
@@ -115,7 +115,7 @@ module Jade
|
|
|
115
115
|
end
|
|
116
116
|
|
|
117
117
|
functions
|
|
118
|
-
.filter_map { generate_function(it, registry, interface, type_name) }
|
|
118
|
+
.filter_map { generate_function(it, registry, interface, type_name, symbol) }
|
|
119
119
|
.join(Pretty.newline(2))
|
|
120
120
|
end
|
|
121
121
|
|
|
@@ -143,7 +143,7 @@ module Jade
|
|
|
143
143
|
fn_map = symbol.functions.filter_map { |fn_name, ref|
|
|
144
144
|
next unless ref.is_a?(Symbol::ValueRef)
|
|
145
145
|
|
|
146
|
-
[fn_name, "->(*args) { #{internal(ref.module_name)}.#{ref
|
|
146
|
+
[fn_name, "->(*args) { #{internal(ref.module_name)}.#{registered_target(ref, registry)}(*args) }"]
|
|
147
147
|
}.to_h
|
|
148
148
|
|
|
149
149
|
return "" if fn_map.empty?
|
|
@@ -155,16 +155,41 @@ module Jade
|
|
|
155
155
|
.join(Pretty.newline)
|
|
156
156
|
end
|
|
157
157
|
|
|
158
|
-
def
|
|
158
|
+
def requirement_markers(body, impl_sym, fn_name, registry)
|
|
159
|
+
required = registry
|
|
160
|
+
.lookup(impl_sym.interface)
|
|
161
|
+
.functions
|
|
162
|
+
.find { it.name == fn_name }
|
|
163
|
+
.constraints
|
|
164
|
+
|
|
165
|
+
return [] if required.empty?
|
|
166
|
+
|
|
167
|
+
body_markers(body)
|
|
168
|
+
.uniq { [it.interface, it.type.id] }
|
|
169
|
+
.sort_by { |c| required.index { |iface, _| iface == c.interface } || required.size }
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def registered_target(ref, registry)
|
|
173
|
+
case registry.lookup(ref)
|
|
174
|
+
in Symbol::Function => fn then FunctionCall.fn_target_name(fn, registry)
|
|
175
|
+
else ref.name
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def generate_function(impl_fn, registry, interface, type_name, impl_sym)
|
|
159
180
|
impl_fn => AST::ImplementationFunction(name: fn_name, fn:)
|
|
160
181
|
|
|
161
182
|
case fn
|
|
162
183
|
in AST::Lambda(params:, body:)
|
|
163
|
-
synth
|
|
164
|
-
|
|
165
|
-
|
|
184
|
+
synth = impl_synthetic_name(interface, type_name, fn_name)
|
|
185
|
+
|
|
186
|
+
dicts, body_code = requirement_markers(body, impl_sym, fn_name, registry)
|
|
187
|
+
.then { with_dict_params(it) { generate_node(body, registry) } }
|
|
166
188
|
|
|
167
|
-
|
|
189
|
+
(params.map { generate_node(it, registry) } + dicts)
|
|
190
|
+
.join(', ')
|
|
191
|
+
.then { it.empty? ? '' : "(#{it})" }
|
|
192
|
+
.then { Pretty.block("def #{synth}#{it}", body_code) }
|
|
168
193
|
|
|
169
194
|
# Bare VariableReference, and the auto-invoke FunctionCall the
|
|
170
195
|
# desugar pass synthesises for zero-arg fn refs, both dispatch via
|
|
@@ -113,7 +113,18 @@ module Jade
|
|
|
113
113
|
extend Helper
|
|
114
114
|
|
|
115
115
|
def format(node, indent:, source:)
|
|
116
|
-
"#{node.name} : #{format_type(node.type)}
|
|
116
|
+
"#{node.name} : #{format_type(node.type)}#{constraints_clause(node)}"
|
|
117
|
+
.then(&and_indent(indent))
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def constraints_clause(node)
|
|
121
|
+
return '' if node.constraints.empty?
|
|
122
|
+
|
|
123
|
+
node
|
|
124
|
+
.constraints
|
|
125
|
+
.map { "#{it.interface}(#{it.type_param.name})" }
|
|
126
|
+
.join(', ')
|
|
127
|
+
.then { " with #{it}" }
|
|
117
128
|
end
|
|
118
129
|
end
|
|
119
130
|
end
|
|
@@ -50,10 +50,14 @@ module Jade
|
|
|
50
50
|
private
|
|
51
51
|
|
|
52
52
|
def build_interface_function(entry, interface_ref, fn_decl)
|
|
53
|
-
fn_decl => AST::InterfaceFunctionDecl(name:, type:, range:)
|
|
53
|
+
fn_decl => AST::InterfaceFunctionDecl(name:, type:, constraints:, range:)
|
|
54
54
|
|
|
55
55
|
figure_out_type(entry, type)
|
|
56
|
-
.
|
|
56
|
+
.and_then do |type_symbol|
|
|
57
|
+
declared_constraints(entry, constraints)
|
|
58
|
+
.map { [type_symbol, it] }
|
|
59
|
+
end
|
|
60
|
+
.map do |(type_symbol, declared)|
|
|
57
61
|
params, return_type =
|
|
58
62
|
case type_symbol
|
|
59
63
|
in Symbol::FunctionType(params:, return_type:)
|
|
@@ -63,9 +67,18 @@ module Jade
|
|
|
63
67
|
end
|
|
64
68
|
|
|
65
69
|
Symbol
|
|
66
|
-
.interface_function(name, interface_ref, params, return_type, range)
|
|
70
|
+
.interface_function(name, interface_ref, params, return_type, range, declared)
|
|
67
71
|
end
|
|
68
72
|
end
|
|
73
|
+
|
|
74
|
+
def declared_constraints(entry, constraints)
|
|
75
|
+
constraints
|
|
76
|
+
.map do |constraint|
|
|
77
|
+
require_type(entry, constraint.interface, constraint.range)
|
|
78
|
+
.map { [it.to_ref.qualified_name, constraint.type_param.name] }
|
|
79
|
+
end
|
|
80
|
+
.then { Results.sequence(it) }
|
|
81
|
+
end
|
|
69
82
|
end
|
|
70
83
|
end
|
|
71
84
|
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module TypeChecking
|
|
4
|
+
module Error
|
|
5
|
+
class CrossModuleRequirement < Jade::Error
|
|
6
|
+
def initialize(entry, span, interface:, fn_name:, constraint:)
|
|
7
|
+
super(entry:, span:)
|
|
8
|
+
@interface = interface
|
|
9
|
+
@fn_name = fn_name
|
|
10
|
+
@constraint = constraint
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def message
|
|
14
|
+
"Implementation of #{@interface}.#{@fn_name} requires #{@constraint}, " \
|
|
15
|
+
"but #{@interface} is declared in another module. A requirement " \
|
|
16
|
+
'can only be added to an interface this module declares'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def label
|
|
20
|
+
"requires #{@constraint}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def notes
|
|
24
|
+
[
|
|
25
|
+
Jade::Diagnostics::Annotation[
|
|
26
|
+
:help,
|
|
27
|
+
'give the function a concrete type for that parameter, or move ' \
|
|
28
|
+
'the implementation into the module that declares the interface',
|
|
29
|
+
],
|
|
30
|
+
]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module TypeChecking
|
|
4
|
+
module Error
|
|
5
|
+
class ImplementationFunctionConstraint < Jade::Error
|
|
6
|
+
def initialize(entry, span, interface:, fn_name:, constraint:)
|
|
7
|
+
super(entry:, span:)
|
|
8
|
+
@interface = interface
|
|
9
|
+
@fn_name = fn_name
|
|
10
|
+
@constraint = constraint
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def message
|
|
14
|
+
"Implementation of #{@interface}.#{@fn_name} requires #{@constraint}, " \
|
|
15
|
+
'whose type is not the one being implemented. An implementation ' \
|
|
16
|
+
'can only require interfaces of the type it implements'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def label
|
|
20
|
+
"requires #{@constraint}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def notes
|
|
24
|
+
[
|
|
25
|
+
Jade::Diagnostics::Annotation[
|
|
26
|
+
:help,
|
|
27
|
+
'give the function a concrete type for that parameter, or move ' \
|
|
28
|
+
'the requirement onto the type being implemented',
|
|
29
|
+
],
|
|
30
|
+
]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module TypeChecking
|
|
4
|
+
module Error
|
|
5
|
+
class UndeclaredRequirement < Jade::Error
|
|
6
|
+
def initialize(entry, span, interface:, fn_name:, constraint:, declared:)
|
|
7
|
+
super(entry:, span:)
|
|
8
|
+
@interface = interface
|
|
9
|
+
@fn_name = fn_name
|
|
10
|
+
@constraint = constraint
|
|
11
|
+
@declared = declared
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def message
|
|
15
|
+
"Implementation of #{@interface}.#{@fn_name} requires #{@constraint}, " \
|
|
16
|
+
"which #{@interface} does not declare. It declares #{@declared}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def label
|
|
20
|
+
"requires #{@constraint}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def notes
|
|
24
|
+
[
|
|
25
|
+
Jade::Diagnostics::Annotation[
|
|
26
|
+
:help,
|
|
27
|
+
"add it to the method: `#{@fn_name} : ... with #{@constraint}`",
|
|
28
|
+
],
|
|
29
|
+
]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -22,4 +22,7 @@ require 'jade/frontend/type_checking/error/record_access_type_mismatch'
|
|
|
22
22
|
require 'jade/frontend/type_checking/error/record_update_type_mismatch'
|
|
23
23
|
require 'jade/frontend/type_checking/error/unresolved_constraint'
|
|
24
24
|
require 'jade/frontend/type_checking/error/implementation_type_mismatch'
|
|
25
|
+
require 'jade/frontend/type_checking/error/implementation_function_constraint'
|
|
26
|
+
require 'jade/frontend/type_checking/error/cross_module_requirement'
|
|
27
|
+
require 'jade/frontend/type_checking/error/undeclared_requirement'
|
|
25
28
|
require 'jade/frontend/type_checking/error/missing_implementation'
|
|
@@ -48,11 +48,6 @@ module Jade
|
|
|
48
48
|
.map { st.env.substitution.apply(it) }
|
|
49
49
|
.uniq { it.type.is_a?(Type::Var) ? [it.interface, it.type] : it }
|
|
50
50
|
|
|
51
|
-
# TODO: for impl function declarations, unresolved constraints here
|
|
52
|
-
# (e.g. Eq(a) when the body calls == on a field of type a) should
|
|
53
|
-
# be stored as impl-level constraints, not function-level ones.
|
|
54
|
-
# The impl finalization pass (see TypeChecking.finalize) should then
|
|
55
|
-
# promote them into deps when the impl is instantiated for a concrete type.
|
|
56
51
|
st.bind(
|
|
57
52
|
symbol.qualified_name,
|
|
58
53
|
Placeholder[
|
|
@@ -27,6 +27,15 @@ module Jade
|
|
|
27
27
|
def type_from_symbol(symbol, registry, var_gen)
|
|
28
28
|
Type.from_symbol(symbol, registry, var_gen)
|
|
29
29
|
end
|
|
30
|
+
|
|
31
|
+
def attach_markers(result)
|
|
32
|
+
result
|
|
33
|
+
.constraints
|
|
34
|
+
.select { it.type.is_a?(Type::Var) && it.index != :unindex }
|
|
35
|
+
.each { Constraints.attach_dictionary(it, it) }
|
|
36
|
+
|
|
37
|
+
result
|
|
38
|
+
end
|
|
30
39
|
end
|
|
31
40
|
end
|
|
32
41
|
end
|
|
@@ -57,6 +57,8 @@ module Jade
|
|
|
57
57
|
st_after_bind,
|
|
58
58
|
Expected.check(expected_type),
|
|
59
59
|
interface_qname,
|
|
60
|
+
concrete_type,
|
|
61
|
+
iface_fn_type,
|
|
60
62
|
)
|
|
61
63
|
end
|
|
62
64
|
.then { [it, Result.init(Type.unit)] }
|
|
@@ -64,7 +66,7 @@ module Jade
|
|
|
64
66
|
|
|
65
67
|
private
|
|
66
68
|
|
|
67
|
-
def infer_fn(impl_fn, registry, state, expected, interface_qname)
|
|
69
|
+
def infer_fn(impl_fn, registry, state, expected, interface_qname, head_type, sig_type)
|
|
68
70
|
impl_fn => AST::ImplementationFunction(name:, fn:)
|
|
69
71
|
|
|
70
72
|
case fn
|
|
@@ -83,29 +85,63 @@ module Jade
|
|
|
83
85
|
state,
|
|
84
86
|
Expected.infer(state.fresh),
|
|
85
87
|
)
|
|
86
|
-
body_state
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
body_state
|
|
89
|
+
.unify(
|
|
90
|
+
body_result.type,
|
|
91
|
+
expected.type,
|
|
92
|
+
&mismatch_error(state.env.entry_name, impl_fn, interface_qname, name)
|
|
93
|
+
)
|
|
94
|
+
.then do |unified|
|
|
95
|
+
record_requirements(
|
|
96
|
+
unified,
|
|
97
|
+
body_result.constraints,
|
|
98
|
+
head_type,
|
|
99
|
+
sig_type,
|
|
100
|
+
interface_qname,
|
|
101
|
+
impl_fn,
|
|
102
|
+
name,
|
|
103
|
+
)
|
|
104
|
+
end
|
|
91
105
|
end
|
|
92
106
|
|
|
93
107
|
in AST::Lambda
|
|
94
|
-
check(fn, registry, state, expected)
|
|
108
|
+
lambda_state, lambda_result = check(fn, registry, state, expected)
|
|
109
|
+
|
|
110
|
+
record_requirements(
|
|
111
|
+
lambda_state,
|
|
112
|
+
lambda_result.constraints,
|
|
113
|
+
head_type,
|
|
114
|
+
sig_type,
|
|
115
|
+
interface_qname,
|
|
116
|
+
impl_fn,
|
|
117
|
+
name,
|
|
118
|
+
)
|
|
95
119
|
|
|
96
120
|
in AST::VariableReference
|
|
97
121
|
ref_state, ref_result = check(fn, registry, state, expected)
|
|
98
122
|
|
|
99
|
-
ref_state
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
123
|
+
ref_state
|
|
124
|
+
.unify(
|
|
125
|
+
ref_result.type,
|
|
126
|
+
expected.type,
|
|
127
|
+
&mismatch_error(
|
|
128
|
+
state.env.entry_name,
|
|
129
|
+
impl_fn,
|
|
130
|
+
interface_qname,
|
|
131
|
+
name
|
|
132
|
+
)
|
|
107
133
|
)
|
|
108
|
-
|
|
134
|
+
.then do |unified|
|
|
135
|
+
record_requirements(
|
|
136
|
+
unified,
|
|
137
|
+
ref_result.constraints,
|
|
138
|
+
head_type,
|
|
139
|
+
sig_type,
|
|
140
|
+
interface_qname,
|
|
141
|
+
impl_fn,
|
|
142
|
+
name,
|
|
143
|
+
)
|
|
144
|
+
end
|
|
109
145
|
|
|
110
146
|
# `decoder: zero_arg_fn` becomes `FunctionCall(zero_arg_fn, [])`
|
|
111
147
|
# after ZeroArgRewrite. Type the call; its result must match the
|
|
@@ -115,6 +151,47 @@ module Jade
|
|
|
115
151
|
end
|
|
116
152
|
end
|
|
117
153
|
|
|
154
|
+
def record_requirements(state, constraints, head_type, sig_type, interface_qname, impl_fn, fn_name)
|
|
155
|
+
substitution = state.env.substitution
|
|
156
|
+
head_vars = substitution.apply(head_type).unbound_vars.map(&:id).to_set
|
|
157
|
+
names = signature_names(sig_type, substitution)
|
|
158
|
+
|
|
159
|
+
free = constraints
|
|
160
|
+
.map { substitution.apply(it) }
|
|
161
|
+
.select { it.type.is_a?(Type::Var) && !head_vars.include?(it.type.id) }
|
|
162
|
+
.uniq { [it.interface, it.type.id] }
|
|
163
|
+
|
|
164
|
+
free
|
|
165
|
+
.reject { names.key?(it.type.id) }
|
|
166
|
+
.map { unsatisfiable(state, impl_fn, interface_qname, fn_name, it) }
|
|
167
|
+
.then { state.add_errors(it) }
|
|
168
|
+
.require_impl(
|
|
169
|
+
[interface_qname, fn_name],
|
|
170
|
+
free.filter_map { |c| names[c.type.id]&.then { |n| [c.interface, n] } },
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def signature_names(sig_type, substitution)
|
|
175
|
+
sig_type
|
|
176
|
+
.unbound_vars
|
|
177
|
+
.filter_map do |var|
|
|
178
|
+
substitution
|
|
179
|
+
.apply(var)
|
|
180
|
+
.then { it.is_a?(Type::Var) ? [it.id, var.name] : nil }
|
|
181
|
+
end
|
|
182
|
+
.to_h
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def unsatisfiable(state, impl_fn, interface_qname, fn_name, constraint)
|
|
186
|
+
Error::ImplementationFunctionConstraint.new(
|
|
187
|
+
state.env.entry_name,
|
|
188
|
+
impl_fn.range,
|
|
189
|
+
interface: interface_qname,
|
|
190
|
+
fn_name:,
|
|
191
|
+
constraint: constraint.to_s,
|
|
192
|
+
)
|
|
193
|
+
end
|
|
194
|
+
|
|
118
195
|
def constructor_var_in?(type, var_id)
|
|
119
196
|
case type
|
|
120
197
|
in Type::Application(constructor: Type::Var(id:)) if id == var_id
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module TypeChecking
|
|
4
|
+
module Requirements
|
|
5
|
+
extend self
|
|
6
|
+
|
|
7
|
+
def reconcile(entry, registry, state)
|
|
8
|
+
pending = state
|
|
9
|
+
.impl_requirements
|
|
10
|
+
.to_h { |key, pairs| [key, pairs - declared_for(key, registry)] }
|
|
11
|
+
.reject { |_, pairs| pairs.empty? }
|
|
12
|
+
|
|
13
|
+
errors(entry, registry, pending)
|
|
14
|
+
.then { return [entry, state.add_errors(it)] if it.any? }
|
|
15
|
+
|
|
16
|
+
amended = amend_all(entry, pending)
|
|
17
|
+
return [entry, state] if amended.equal?(entry)
|
|
18
|
+
|
|
19
|
+
rebind(state.env, amended, registry)
|
|
20
|
+
.then { recheck(entry, registry, it) }
|
|
21
|
+
.then { |rechecked| [amended, rechecked] }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def recheck(entry, registry, env)
|
|
27
|
+
TypeChecking
|
|
28
|
+
.check_node(entry.ast, registry, State.init(env), Expected.infer(env.fresh))
|
|
29
|
+
.first
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def rebind(env, entry, registry)
|
|
33
|
+
entry
|
|
34
|
+
.defined_values
|
|
35
|
+
.each_value
|
|
36
|
+
.select { it.is_a?(Symbol::InterfaceFunction) && it.constraints.any? }
|
|
37
|
+
.reduce(env) do |acc, sym|
|
|
38
|
+
Type
|
|
39
|
+
.from_symbol(sym, registry, acc.var_gen)
|
|
40
|
+
.then { Inference::Helpers.generalize(acc, *it) }
|
|
41
|
+
.then { acc.bind(sym.qualified_name, it) }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def declared_for((interface, fn_name), registry)
|
|
46
|
+
Symbol
|
|
47
|
+
.type_ref_from_qualified_name(interface)
|
|
48
|
+
.then { registry.lookup(it) }
|
|
49
|
+
.then { it.is_a?(Symbol::Interface) ? it.functions : [] }
|
|
50
|
+
.find { it.name == fn_name }
|
|
51
|
+
&.constraints || []
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def errors(entry, registry, pending)
|
|
55
|
+
pending.flat_map do |(interface, fn_name), constraints|
|
|
56
|
+
declared = declared_for([interface, fn_name], registry)
|
|
57
|
+
next [] if declared.empty? && local?(entry, interface)
|
|
58
|
+
|
|
59
|
+
constraints.map do |iface, var|
|
|
60
|
+
error_for(entry, interface, fn_name, "#{iface} #{var}", declared)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def local?(entry, interface)
|
|
66
|
+
entry.defined_types.key?(interface.split('.').last)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def error_for(entry, interface, fn_name, constraint, declared)
|
|
70
|
+
return cross_module_error(entry, interface, fn_name, constraint) if declared.empty?
|
|
71
|
+
|
|
72
|
+
Error::UndeclaredRequirement.new(
|
|
73
|
+
entry.name,
|
|
74
|
+
nil,
|
|
75
|
+
interface:,
|
|
76
|
+
fn_name:,
|
|
77
|
+
constraint:,
|
|
78
|
+
declared: declared.map { |iface, var| "#{iface} #{var}" }.join(', '),
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def cross_module_error(entry, interface, fn_name, constraint)
|
|
83
|
+
Error::CrossModuleRequirement.new(
|
|
84
|
+
entry.name,
|
|
85
|
+
nil,
|
|
86
|
+
interface:,
|
|
87
|
+
fn_name:,
|
|
88
|
+
constraint:,
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def amend_all(entry, pending)
|
|
93
|
+
pending.reduce(entry) do |acc, ((interface, fn_name), constraints)|
|
|
94
|
+
amend_method(acc, interface, fn_name, constraints)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def amend_method(entry, interface_qname, fn_name, constraints)
|
|
99
|
+
symbol = entry.defined_types[interface_qname.split('.').last]
|
|
100
|
+
return entry unless symbol.is_a?(Symbol::Interface)
|
|
101
|
+
|
|
102
|
+
symbol
|
|
103
|
+
.functions
|
|
104
|
+
.map { it.name == fn_name ? with_constraints(it, constraints) : it }
|
|
105
|
+
.then { symbol.with(functions: it) }
|
|
106
|
+
.then { |iface| iface.functions.reduce(entry.define(iface)) { |acc, fn| acc.define(fn) } }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def with_constraints(fn, constraints)
|
|
110
|
+
fn.with(constraints: (fn.constraints + constraints).uniq)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
module Jade
|
|
2
2
|
module Frontend
|
|
3
3
|
module TypeChecking
|
|
4
|
-
State = Data.define(:env, :errors, :skip_constraints) do
|
|
4
|
+
State = Data.define(:env, :errors, :skip_constraints, :impl_requirements) do
|
|
5
5
|
def self.init(env, skip_constraints: false)
|
|
6
|
-
new(env, [], skip_constraints)
|
|
6
|
+
new(env, [], skip_constraints, {})
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def require_impl(key, constraints)
|
|
10
|
+
return self if constraints.empty?
|
|
11
|
+
|
|
12
|
+
impl_requirements
|
|
13
|
+
.merge(key => constraints) { |_, old, new| (old + new).uniq }
|
|
14
|
+
.then { with(impl_requirements: it) }
|
|
7
15
|
end
|
|
8
16
|
|
|
9
17
|
def unify_result(result, right, rigid_vars = [], &block)
|
|
@@ -8,6 +8,7 @@ require 'jade/frontend/type_checking/expected'
|
|
|
8
8
|
require 'jade/frontend/type_checking/inference'
|
|
9
9
|
require 'jade/frontend/type_checking/loader'
|
|
10
10
|
require 'jade/frontend/type_checking/port_resolution'
|
|
11
|
+
require 'jade/frontend/type_checking/requirements'
|
|
11
12
|
require 'jade/frontend/type_checking/result'
|
|
12
13
|
require 'jade/frontend/type_checking/state'
|
|
13
14
|
require 'jade/frontend/type_checking/substitution'
|
|
@@ -26,11 +27,16 @@ module Jade
|
|
|
26
27
|
.load(entry, registry)
|
|
27
28
|
.then { collect_constraints(entry, registry, it) }
|
|
28
29
|
.then { check_node(entry.ast, registry, State.init(it), Expected.infer(it.fresh)) }
|
|
29
|
-
.then {
|
|
30
|
+
.then { |state, _| Requirements.reconcile(entry, registry, state) }
|
|
31
|
+
.then { |amended, state| complete(amended, state, registry) }
|
|
32
|
+
.and_then { PortResolution.resolve(it, registry) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def complete(entry, state, registry)
|
|
36
|
+
finalize(state, registry)
|
|
30
37
|
.map { Canonicalize.run(entry.ast, it, registry) }
|
|
31
38
|
.map { it.canonicalize_node_types }
|
|
32
39
|
.map { entry.with(env: it) }
|
|
33
|
-
.and_then { PortResolution.resolve(it, registry) }
|
|
34
40
|
end
|
|
35
41
|
|
|
36
42
|
# A collecting pass walks declarations in source order, so a call to a
|
|
@@ -76,7 +82,7 @@ module Jade
|
|
|
76
82
|
.then { it + 1 }
|
|
77
83
|
end
|
|
78
84
|
|
|
79
|
-
def finalize(state,
|
|
85
|
+
def finalize(state, registry)
|
|
80
86
|
state.env => { bindings:, entry_name: }
|
|
81
87
|
|
|
82
88
|
errors = bindings
|
data/lib/jade/parsing.rb
CHANGED
|
@@ -753,10 +753,23 @@ module Jade
|
|
|
753
753
|
}
|
|
754
754
|
|
|
755
755
|
parser(:interface_function_decl) {
|
|
756
|
+
constraint =
|
|
757
|
+
(
|
|
758
|
+
constant >>
|
|
759
|
+
type(:lparen).skip >>
|
|
760
|
+
type_param >>
|
|
761
|
+
type(:rparen).skip
|
|
762
|
+
).map(&AST.interface_constraint)
|
|
763
|
+
|
|
764
|
+
constraints =
|
|
765
|
+
type(:with).skip >>
|
|
766
|
+
at_least_one(constraint, separated_by: type(:comma).skip)
|
|
767
|
+
|
|
756
768
|
(
|
|
757
769
|
(type(:identifier) | (type(:lparen).skip >> operator >> type(:rparen).skip)) >>
|
|
758
770
|
type(:colon).skip >>
|
|
759
|
-
type_expression
|
|
771
|
+
type_expression >>
|
|
772
|
+
optional(constraints, default: []).map { [it] }
|
|
760
773
|
).map(&AST.interface_function_decl)
|
|
761
774
|
}
|
|
762
775
|
|
data/lib/jade/symbol.rb
CHANGED
|
@@ -134,8 +134,8 @@ module Jade
|
|
|
134
134
|
Interface[nil, name, type_var, functions, default, span]
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
-
def interface_function(name, inteface, params, return_type, span)
|
|
138
|
-
InterfaceFunction[nil, name, inteface, params, return_type, span]
|
|
137
|
+
def interface_function(name, inteface, params, return_type, span, constraints = [])
|
|
138
|
+
InterfaceFunction[nil, name, inteface, params, return_type, span, constraints]
|
|
139
139
|
end
|
|
140
140
|
|
|
141
141
|
def implementation(
|
data/lib/jade/type.rb
CHANGED
|
@@ -178,9 +178,18 @@ module Jade
|
|
|
178
178
|
constraint = Type
|
|
179
179
|
.constraint(symbol.interface.qualified_name, param_type, nil)
|
|
180
180
|
|
|
181
|
-
|
|
182
|
-
.
|
|
183
|
-
|
|
181
|
+
ret_type, ret_cs, full_map =
|
|
182
|
+
from_symbol_r(symbol.return_type, registry, var_gen, local_map)
|
|
183
|
+
|
|
184
|
+
extra_cs = symbol.constraints.map { |iface, var_name|
|
|
185
|
+
Type.constraint(iface, full_map.fetch(var_name), nil)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
[
|
|
189
|
+
args.empty? ? ret_type : Type.function(args, ret_type),
|
|
190
|
+
ret_cs + arg_cs + [constraint] + extra_cs,
|
|
191
|
+
var_map,
|
|
192
|
+
]
|
|
184
193
|
|
|
185
194
|
in Symbol::Constructor if symbol.args.empty?
|
|
186
195
|
from_symbol_r(symbol.parent, registry, var_gen, var_map)
|
data/lib/jade/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jade-lang
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Agustin Cornu
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-09-
|
|
10
|
+
date: 2026-09-15 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: base64
|
|
@@ -249,6 +249,7 @@ files:
|
|
|
249
249
|
- lib/jade/frontend/type_checking/env.rb
|
|
250
250
|
- lib/jade/frontend/type_checking/error.rb
|
|
251
251
|
- lib/jade/frontend/type_checking/error/case_of_branches_type_mismatch.rb
|
|
252
|
+
- lib/jade/frontend/type_checking/error/cross_module_requirement.rb
|
|
252
253
|
- lib/jade/frontend/type_checking/error/derivation_failed.rb
|
|
253
254
|
- lib/jade/frontend/type_checking/error/display.rb
|
|
254
255
|
- lib/jade/frontend/type_checking/error/division_by_zero.rb
|
|
@@ -258,6 +259,7 @@ files:
|
|
|
258
259
|
- lib/jade/frontend/type_checking/error/if_branch_type_mismatch.rb
|
|
259
260
|
- lib/jade/frontend/type_checking/error/if_branches_type_mismatch.rb
|
|
260
261
|
- lib/jade/frontend/type_checking/error/if_condition_type_mismatch.rb
|
|
262
|
+
- lib/jade/frontend/type_checking/error/implementation_function_constraint.rb
|
|
261
263
|
- lib/jade/frontend/type_checking/error/implementation_type_mismatch.rb
|
|
262
264
|
- lib/jade/frontend/type_checking/error/list_item_type_mismatch.rb
|
|
263
265
|
- lib/jade/frontend/type_checking/error/missing_implementation.rb
|
|
@@ -270,6 +272,7 @@ files:
|
|
|
270
272
|
- lib/jade/frontend/type_checking/error/record_update_type_mismatch.rb
|
|
271
273
|
- lib/jade/frontend/type_checking/error/recursive_derivation.rb
|
|
272
274
|
- lib/jade/frontend/type_checking/error/type_mismatch.rb
|
|
275
|
+
- lib/jade/frontend/type_checking/error/undeclared_requirement.rb
|
|
273
276
|
- lib/jade/frontend/type_checking/error/unreachable_branch.rb
|
|
274
277
|
- lib/jade/frontend/type_checking/error/unresolved_constraint.rb
|
|
275
278
|
- lib/jade/frontend/type_checking/expected.rb
|
|
@@ -308,6 +311,7 @@ files:
|
|
|
308
311
|
- lib/jade/frontend/type_checking/loader.rb
|
|
309
312
|
- lib/jade/frontend/type_checking/placeholder.rb
|
|
310
313
|
- lib/jade/frontend/type_checking/port_resolution.rb
|
|
314
|
+
- lib/jade/frontend/type_checking/requirements.rb
|
|
311
315
|
- lib/jade/frontend/type_checking/result.rb
|
|
312
316
|
- lib/jade/frontend/type_checking/scheme.rb
|
|
313
317
|
- lib/jade/frontend/type_checking/state.rb
|