jade-lang 0.3.1 → 0.5.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/AGENTS.md +164 -0
- data/CHANGELOG.md +215 -1
- data/README.md +17 -12
- data/docs/interop.md +208 -0
- data/docs/json.md +163 -0
- data/docs/lsp.md +105 -0
- data/docs/stdlib.md +69 -0
- data/docs/syntax.md +458 -0
- data/docs/testing.md +70 -0
- data/lib/jade/api.rb +244 -0
- data/lib/jade/cli/check.rb +97 -0
- data/lib/jade/cli/q.rb +47 -1
- data/lib/jade/cli.rb +4 -2
- data/lib/jade/codegen/emitter.rb +11 -2
- data/lib/jade/codegen/function_call.rb +1 -1
- data/lib/jade/codegen/helpers.rb +6 -0
- data/lib/jade/codegen/inlines.rb +18 -0
- data/lib/jade/codegen/{port_decoder.rb → port_codec.rb} +27 -15
- data/lib/jade/codegen.rb +2 -2
- data/lib/jade/debug.rb +59 -0
- data/lib/jade/decode.rb +332 -212
- data/lib/jade/diagnostics/renderer.rb +16 -5
- data/lib/jade/frontend/fixity_fixer.rb +3 -2
- data/lib/jade/frontend/forward_declaration/interop_import_declaration.rb +13 -3
- data/lib/jade/frontend/pattern_analysis/matrix.rb +57 -23
- data/lib/jade/frontend/semantic_analysis/constructor_reference.rb +33 -7
- data/lib/jade/frontend/semantic_analysis/error/constructor_not_found.rb +20 -5
- data/lib/jade/frontend/semantic_analysis/error/variable_not_found.rb +9 -2
- data/lib/jade/frontend/semantic_analysis/member_access.rb +11 -1
- data/lib/jade/frontend/type_checking/constraints/deriving/decodable.rb +35 -32
- data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +32 -50
- data/lib/jade/frontend/type_checking/constraints/deriving/eq.rb +51 -175
- data/lib/jade/frontend/type_checking/constraints/deriving/helpers.rb +133 -0
- data/lib/jade/frontend/type_checking/constraints/deriving/show.rb +186 -0
- data/lib/jade/frontend/type_checking/constraints/deriving.rb +2 -1
- data/lib/jade/frontend/type_checking/error/port_not_encodable.rb +38 -0
- data/lib/jade/frontend/type_checking/port_resolution.rb +64 -16
- data/lib/jade/interop/boundary.rb +2 -3
- data/lib/jade/interop/runtime.rb +10 -2
- data/lib/jade/lsp/converters.rb +2 -15
- data/lib/jade/lsp/snippets.rb +21 -3
- data/lib/jade/parsing/error.rb +19 -0
- data/lib/jade/runtime.rb +1 -0
- data/lib/jade/signature.rb +47 -0
- data/lib/jade/stdlib/calendar.rb +20 -19
- data/lib/jade/stdlib/clock.rb +7 -13
- data/lib/jade/stdlib/debug.rb +12 -0
- data/lib/jade/stdlib/decimal.rb +3 -16
- data/lib/jade/stdlib/decode.rb +57 -12
- data/lib/jade/stdlib/encode.rb +26 -0
- data/lib/jade/stdlib/intrinsics.rb +12 -1
- data/lib/jade/stdlib/result.rb +1 -1
- data/lib/jade/stdlib/show.rb +40 -0
- data/lib/jade/stdlib/text.rb +131 -0
- data/lib/jade/stdlib.rb +5 -2
- data/lib/jade/symbol/interop_function.rb +3 -1
- data/lib/jade/symbol.rb +2 -2
- data/lib/jade/task.rb +2 -3
- data/lib/jade/version.rb +1 -1
- metadata +19 -3
|
@@ -12,157 +12,56 @@ module Jade
|
|
|
12
12
|
def supports?(interface) = interface == INTERFACE
|
|
13
13
|
|
|
14
14
|
def derive(constraint, registry, entry_name, &lookup)
|
|
15
|
-
|
|
15
|
+
resolve_constraint(constraint, registry, entry_name, lookup)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
private
|
|
19
19
|
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
.on_err { return Err[it] } => Ok(impl)
|
|
20
|
+
def derive_union(constraint, symbol, registry, lookup, entry_name)
|
|
21
|
+
type_vars = symbol.type_params.map(&:name)
|
|
22
|
+
index_map = type_vars.each_with_index.map.to_h
|
|
23
|
+
variants = symbol.variants.map { registry.lookup(it) }
|
|
24
|
+
|
|
25
|
+
concrete = variants
|
|
26
|
+
.flat_map(&:args)
|
|
27
|
+
.reject { it in Symbol::Variable }
|
|
28
|
+
.map { instantiate(it, {}, registry) }
|
|
29
|
+
.uniq
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
|
|
32
|
+
concrete
|
|
33
|
+
.map { lookup.call(Type.constraint(INTERFACE, it, constraint.origin)) }
|
|
34
|
+
.then { Results.sequence(it) }
|
|
35
|
+
.and_then do |deps|
|
|
36
|
+
cases = variants.map {
|
|
37
|
+
build_variant_case(it, index_map, concrete, registry, constraint.origin)
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
Symbol::
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
functions: impl.functions,
|
|
48
|
-
deps: resolved_deps,
|
|
49
|
-
extends: [],
|
|
50
|
-
decl_span: nil,
|
|
40
|
+
eq_fn = Symbol::DerivedFunction.new(
|
|
41
|
+
params: ["one", "other"],
|
|
42
|
+
body: [:case,
|
|
43
|
+
[:list, [[:var, "one"], [:var, "other"]]],
|
|
44
|
+
cases + [[[:_], [false]]],
|
|
45
|
+
],
|
|
51
46
|
)
|
|
52
47
|
|
|
53
|
-
|
|
54
|
-
impl
|
|
48
|
+
Ok[build_union_impl(constraint, type_vars, concrete, eq_fn, deps)]
|
|
55
49
|
end
|
|
56
|
-
.then { Ok[it] }
|
|
57
|
-
|
|
58
|
-
in [INTERFACE, Type::AnonymousRecord(fields:)]
|
|
59
|
-
derive_record_eq(constraint, fields, lookup)
|
|
60
|
-
|
|
61
|
-
else
|
|
62
|
-
Err[
|
|
63
|
-
Error::DerivationFailed
|
|
64
|
-
.new(entry_name, constraint.origin.range, constraint:, trace: [])
|
|
65
|
-
]
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def dependencies_of(impl, args)
|
|
70
|
-
subst = impl
|
|
71
|
-
.type_params
|
|
72
|
-
.map(&:id)
|
|
73
|
-
.zip(args)
|
|
74
|
-
.to_h
|
|
75
|
-
|
|
76
|
-
impl
|
|
77
|
-
.constraints
|
|
78
|
-
.map { it.with(type: substitute_type(it.type, subst)) }
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def substitute_type(type, subst)
|
|
82
|
-
case type
|
|
83
|
-
|
|
84
|
-
in Type::Var(id:)
|
|
85
|
-
subst.fetch(id, type)
|
|
86
|
-
|
|
87
|
-
in Type::Application(constructor:, args:)
|
|
88
|
-
Type::Application.new(
|
|
89
|
-
constructor: constructor,
|
|
90
|
-
args: args.map { substitute_type(it, subst) }
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
in Type::AnonymousRecord(fields:)
|
|
94
|
-
Type::AnonymousRecord.new(
|
|
95
|
-
fields: fields.transform_values { substitute_type(it, subst) }
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
else
|
|
99
|
-
type
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def derive_for_type(constraint, constructor, args, registry, lookup, entry_name)
|
|
104
|
-
symbol =
|
|
105
|
-
Symbol
|
|
106
|
-
.type_ref_from_qualified_name(constructor.name)
|
|
107
|
-
.then { registry.lookup(it) }
|
|
108
|
-
|
|
109
|
-
case symbol
|
|
110
|
-
in Symbol::Union
|
|
111
|
-
Ok[derive_union_eq(constraint, symbol, registry, lookup)]
|
|
112
|
-
|
|
113
|
-
in Symbol::Struct
|
|
114
|
-
derive_struct_eq(constraint, symbol, args, registry, lookup, entry_name)
|
|
115
|
-
|
|
116
|
-
else
|
|
117
|
-
Err[
|
|
118
|
-
Error::DerivationFailed
|
|
119
|
-
.new(entry_name, constraint.origin.range, constraint:, trace: [])
|
|
120
|
-
]
|
|
121
|
-
end
|
|
122
50
|
end
|
|
123
51
|
|
|
124
|
-
def
|
|
125
|
-
|
|
126
|
-
index_map = type_vars.each_with_index.map.to_h
|
|
127
|
-
|
|
128
|
-
cases = symbol.variants
|
|
129
|
-
.map { registry.lookup(it) }
|
|
130
|
-
.map { build_variant_case(it, index_map, lookup, constraint.origin) }
|
|
52
|
+
def build_union_impl(constraint, type_vars, concrete, eq_fn, deps)
|
|
53
|
+
return implementation(constraint, { '(==)' => eq_fn }, deps:) if type_vars.empty?
|
|
131
54
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
55
|
+
Symbol::ImplementationTemplate.new(
|
|
56
|
+
interface: Symbol.type_ref_from_qualified_name(constraint.interface),
|
|
57
|
+
type: constraint.type,
|
|
58
|
+
type_params: type_vars.map { Type.var(it) },
|
|
59
|
+
constraints: union_constraints(constraint, type_vars, concrete),
|
|
60
|
+
functions: { '(==)' => eq_fn },
|
|
138
61
|
)
|
|
139
|
-
|
|
140
|
-
if type_vars.empty?
|
|
141
|
-
Symbol::Implementation.new(
|
|
142
|
-
module_name: nil,
|
|
143
|
-
interface: Symbol.type_ref_from_qualified_name(constraint.interface),
|
|
144
|
-
type: constraint.type,
|
|
145
|
-
type_params: [],
|
|
146
|
-
constraints: [],
|
|
147
|
-
functions: { '(==)' => eq_fn },
|
|
148
|
-
deps: [],
|
|
149
|
-
extends: [],
|
|
150
|
-
decl_span: nil,
|
|
151
|
-
)
|
|
152
|
-
else
|
|
153
|
-
Symbol::ImplementationTemplate.new(
|
|
154
|
-
interface: Symbol.type_ref_from_qualified_name(constraint.interface),
|
|
155
|
-
type: constraint.type,
|
|
156
|
-
type_params: type_vars.map { Type.var(it) },
|
|
157
|
-
constraints: type_vars.map {
|
|
158
|
-
Type.constraint(INTERFACE, Type.var(it), constraint.origin)
|
|
159
|
-
},
|
|
160
|
-
functions: { '(==)' => eq_fn },
|
|
161
|
-
)
|
|
162
|
-
end
|
|
163
62
|
end
|
|
164
63
|
|
|
165
|
-
def build_variant_case(variant, index_map,
|
|
64
|
+
def build_variant_case(variant, index_map, concrete, registry, origin)
|
|
166
65
|
field_count = variant.args.length
|
|
167
66
|
|
|
168
67
|
left_vars = (0...field_count).map { |i| "l#{i}" }
|
|
@@ -173,24 +72,17 @@ module Jade
|
|
|
173
72
|
|
|
174
73
|
comparisons =
|
|
175
74
|
variant.args.each_with_index.map do |arg_type, i|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
]
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
lookup.call(Type.constraint(INTERFACE, arg_type, origin)) => Ok[impl]
|
|
188
|
-
|
|
189
|
-
[:call,
|
|
190
|
-
[:impl_value, impl, "(==)"],
|
|
191
|
-
[[:var, left_vars[i]], [:var, right_vars[i]]]
|
|
192
|
-
]
|
|
193
|
-
end
|
|
75
|
+
idx =
|
|
76
|
+
case arg_type
|
|
77
|
+
in Symbol::Variable(name:) then index_map[name]
|
|
78
|
+
else
|
|
79
|
+
index_map.size + concrete.index(instantiate(arg_type, {}, registry))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
[:call,
|
|
83
|
+
[:impl_arg, idx, "(==)"],
|
|
84
|
+
[[:var, left_vars[i]], [:var, right_vars[i]]]
|
|
85
|
+
]
|
|
194
86
|
end
|
|
195
87
|
|
|
196
88
|
body =
|
|
@@ -204,7 +96,7 @@ module Jade
|
|
|
204
96
|
[[:list, [left_pattern, right_pattern]], body]
|
|
205
97
|
end
|
|
206
98
|
|
|
207
|
-
def
|
|
99
|
+
def derive_record(constraint, fields, lookup)
|
|
208
100
|
field_types = fields.values
|
|
209
101
|
field_keys = fields.keys
|
|
210
102
|
|
|
@@ -221,7 +113,7 @@ module Jade
|
|
|
221
113
|
end
|
|
222
114
|
end
|
|
223
115
|
|
|
224
|
-
def
|
|
116
|
+
def derive_struct(constraint, struct_sym, type_args, registry, lookup, entry_name)
|
|
225
117
|
fields = struct_fields(struct_sym, type_args, registry)
|
|
226
118
|
field_types = fields.map { |_, t| t }
|
|
227
119
|
field_names = fields.map { |k, _| k.to_s }
|
|
@@ -236,27 +128,11 @@ module Jade
|
|
|
236
128
|
end
|
|
237
129
|
end
|
|
238
130
|
|
|
239
|
-
def resolve_field_deps(field_types, lookup, origin)
|
|
240
|
-
field_types
|
|
241
|
-
.map { lookup.call(Type.constraint(INTERFACE, it, origin)) }
|
|
242
|
-
.then { Results.sequence(it) }
|
|
243
|
-
end
|
|
244
|
-
|
|
245
131
|
def build_record_impl(constraint, comparisons, deps)
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
module_name: nil,
|
|
251
|
-
interface: Symbol.type_ref_from_qualified_name(constraint.interface),
|
|
252
|
-
type: constraint.type,
|
|
253
|
-
type_params: [],
|
|
254
|
-
constraints: [],
|
|
255
|
-
functions: { '(==)' => eq_fn },
|
|
256
|
-
deps:,
|
|
257
|
-
extends: [],
|
|
258
|
-
decl_span: nil,
|
|
259
|
-
)
|
|
132
|
+
comparisons
|
|
133
|
+
.then { it.empty? ? true : it.reduce { |a, b| [:and, a, b] } }
|
|
134
|
+
.then { Symbol::DerivedFunction.new(params: ['one', 'other'], body: it) }
|
|
135
|
+
.then { implementation(constraint, { '(==)' => it }, deps:) }
|
|
260
136
|
end
|
|
261
137
|
end
|
|
262
138
|
end
|
|
@@ -33,6 +33,139 @@ module Jade
|
|
|
33
33
|
.map { |name, sym| [name, instantiate(sym, subst, registry)] }
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
def failed(constraint, entry_name)
|
|
37
|
+
Err[
|
|
38
|
+
Error::DerivationFailed
|
|
39
|
+
.new(entry_name, constraint.origin.range, constraint:, trace: [])
|
|
40
|
+
]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def implementation(constraint, functions, deps: [], type_params: [], constraints: [])
|
|
44
|
+
Symbol::Implementation.new(
|
|
45
|
+
module_name: nil,
|
|
46
|
+
interface: Symbol.type_ref_from_qualified_name(constraint.interface),
|
|
47
|
+
type: constraint.type,
|
|
48
|
+
type_params:,
|
|
49
|
+
constraints:,
|
|
50
|
+
functions:,
|
|
51
|
+
deps:,
|
|
52
|
+
extends: [],
|
|
53
|
+
decl_span: nil,
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def resolve_field_deps(field_types, lookup, origin)
|
|
58
|
+
field_types
|
|
59
|
+
.map { lookup.call(Type.constraint(self::INTERFACE, it, origin)) }
|
|
60
|
+
.then { Results.sequence(it) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def resolve_constraint(constraint, registry, entry_name, lookup)
|
|
64
|
+
special = special_case(constraint, lookup)
|
|
65
|
+
return special if special
|
|
66
|
+
|
|
67
|
+
case constraint.type
|
|
68
|
+
in Type::Application(constructor:, args:)
|
|
69
|
+
dispatch_type(constraint, constructor, args, registry, lookup, entry_name)
|
|
70
|
+
.on_err { return Err[it] } => Ok(impl)
|
|
71
|
+
|
|
72
|
+
case impl
|
|
73
|
+
in Symbol::ImplementationTemplate
|
|
74
|
+
instantiate_template(constraint, impl, args, lookup)
|
|
75
|
+
else
|
|
76
|
+
Ok[impl]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
in Type::AnonymousRecord(fields:)
|
|
80
|
+
derive_record(constraint, fields, lookup)
|
|
81
|
+
|
|
82
|
+
else
|
|
83
|
+
failed(constraint, entry_name)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def special_case(_constraint, _lookup)
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def dispatch_type(constraint, constructor, args, registry, lookup, entry_name)
|
|
92
|
+
existing = registry.implementations[[constraint.interface, constructor.name]]
|
|
93
|
+
return Ok[existing] if existing
|
|
94
|
+
|
|
95
|
+
Symbol
|
|
96
|
+
.type_ref_from_qualified_name(constructor.name)
|
|
97
|
+
.then { registry.lookup(it) }
|
|
98
|
+
.then do |symbol|
|
|
99
|
+
case symbol
|
|
100
|
+
in Symbol::Union
|
|
101
|
+
derive_union(constraint, symbol, registry, lookup, entry_name)
|
|
102
|
+
|
|
103
|
+
in Symbol::Struct
|
|
104
|
+
derive_struct(constraint, symbol, args, registry, lookup, entry_name)
|
|
105
|
+
|
|
106
|
+
else
|
|
107
|
+
failed(constraint, entry_name)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def instantiate_template(constraint, impl, args, lookup)
|
|
113
|
+
deps = dependencies_of(impl, args)
|
|
114
|
+
resolved = deps.filter_map { |dep|
|
|
115
|
+
next if dep.type in Type::Var
|
|
116
|
+
lookup.call(dep).on_err { return Err[it] } => Ok[found]
|
|
117
|
+
found
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
Ok[
|
|
121
|
+
implementation(
|
|
122
|
+
constraint, impl.functions,
|
|
123
|
+
deps: resolved, type_params: args, constraints: deps,
|
|
124
|
+
)
|
|
125
|
+
]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def union_constraints(constraint, type_vars, concrete)
|
|
129
|
+
type_vars
|
|
130
|
+
.map { Type.var(it) }
|
|
131
|
+
.concat(concrete)
|
|
132
|
+
.map { Type.constraint(self::INTERFACE, it, constraint.origin) }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def dependencies_of(impl, args)
|
|
136
|
+
subst = impl
|
|
137
|
+
.type_params
|
|
138
|
+
.map(&:id)
|
|
139
|
+
.zip(args)
|
|
140
|
+
.to_h
|
|
141
|
+
|
|
142
|
+
impl
|
|
143
|
+
.constraints
|
|
144
|
+
.map { it.with(type: substitute_type(it.type, subst)) }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def substitute_type(type, subst)
|
|
148
|
+
case type
|
|
149
|
+
|
|
150
|
+
in Type::Var(id:)
|
|
151
|
+
subst.fetch(id, type)
|
|
152
|
+
|
|
153
|
+
in Type::Application(constructor:, args:)
|
|
154
|
+
Type::Application.new(
|
|
155
|
+
constructor: constructor,
|
|
156
|
+
args: args.map { substitute_type(it, subst) }
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
in Type::AnonymousRecord(fields:)
|
|
160
|
+
Type::AnonymousRecord.new(
|
|
161
|
+
fields: fields.transform_values { substitute_type(it, subst) }
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
else
|
|
165
|
+
type
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
36
169
|
def instantiate(sym, subst, registry)
|
|
37
170
|
case sym
|
|
38
171
|
in Symbol::Variable(name:)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module TypeChecking
|
|
4
|
+
module Constraints
|
|
5
|
+
module Deriving
|
|
6
|
+
module Show
|
|
7
|
+
extend self
|
|
8
|
+
include Helpers
|
|
9
|
+
|
|
10
|
+
INTERFACE = 'Show.Show'
|
|
11
|
+
|
|
12
|
+
def supports?(interface) = interface == INTERFACE
|
|
13
|
+
|
|
14
|
+
def derive(constraint, registry, entry_name, &lookup)
|
|
15
|
+
resolve_constraint(constraint, registry, entry_name, lookup)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
# List has no variants to walk, so it is matched by name here rather
|
|
21
|
+
# than declared as an instance in the Show module. That is a stopgap:
|
|
22
|
+
# a parameterised stdlib instance needs a registration path the DSL
|
|
23
|
+
# does not have. Encodable hardcodes List and Maybe for the same
|
|
24
|
+
# reason. Contained because derivation is private — see
|
|
25
|
+
# vault jade/plans/jade-test-runner.md for the four layers involved.
|
|
26
|
+
LIST = 'List.List'
|
|
27
|
+
|
|
28
|
+
def special_case(constraint, lookup)
|
|
29
|
+
case constraint.type
|
|
30
|
+
in Type::Function
|
|
31
|
+
Ok[implementation(constraint, { 'show' => constant('<function>') })]
|
|
32
|
+
|
|
33
|
+
in Type::Application(constructor: Type::Constructor(name: LIST), args: [inner])
|
|
34
|
+
list_show(constraint, inner, lookup)
|
|
35
|
+
|
|
36
|
+
else
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def list_show(constraint, inner, lookup)
|
|
42
|
+
lookup
|
|
43
|
+
.call(Type.constraint(INTERFACE, inner, constraint.origin))
|
|
44
|
+
.and_then do |dep|
|
|
45
|
+
body = concat([
|
|
46
|
+
'[',
|
|
47
|
+
[:call, [:stdlib_fn, 'String.join'], [
|
|
48
|
+
[:call, [:stdlib_fn, 'List.map'], [[:var, 'list'], [:impl_arg, 0, 'show']]],
|
|
49
|
+
', ',
|
|
50
|
+
]],
|
|
51
|
+
']',
|
|
52
|
+
])
|
|
53
|
+
|
|
54
|
+
Symbol::DerivedFunction
|
|
55
|
+
.new(params: ['list'], body:)
|
|
56
|
+
.then { Ok[implementation(constraint, { 'show' => it }, deps: [dep])] }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def constant(text)
|
|
61
|
+
Symbol::DerivedFunction.new(params: ['value'], body: text)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def concat(parts)
|
|
65
|
+
[:call, [:stdlib_fn, 'String.concat'], [[:list, parts]]]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def shown(dict_index, expr)
|
|
69
|
+
[:call, [:impl_arg, dict_index, 'show'], [expr]]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def derive_union(constraint, symbol, registry, lookup, entry_name)
|
|
73
|
+
type_vars = symbol.type_params.map(&:name)
|
|
74
|
+
index_map = type_vars.each_with_index.map.to_h
|
|
75
|
+
variants = symbol.variants.map { registry.lookup(it) }
|
|
76
|
+
|
|
77
|
+
concrete = variants
|
|
78
|
+
.flat_map(&:args)
|
|
79
|
+
.reject { it in Symbol::Variable }
|
|
80
|
+
.map { instantiate(it, {}, registry) }
|
|
81
|
+
.uniq
|
|
82
|
+
|
|
83
|
+
return failed(constraint, entry_name) if variants.empty?
|
|
84
|
+
|
|
85
|
+
concrete
|
|
86
|
+
.map { lookup.call(Type.constraint(INTERFACE, it, constraint.origin)) }
|
|
87
|
+
.then { Results.sequence(it) }
|
|
88
|
+
.and_then do |deps|
|
|
89
|
+
cases = variants.map {
|
|
90
|
+
variant_case(it, index_map, concrete, registry)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
show_fn = Symbol::DerivedFunction.new(
|
|
94
|
+
params: ['value'],
|
|
95
|
+
body: [:case, [:var, 'value'], cases],
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
Ok[union_impl(constraint, type_vars, concrete, show_fn, deps)]
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def union_impl(constraint, type_vars, concrete, show_fn, deps)
|
|
103
|
+
return implementation(constraint, { 'show' => show_fn }, deps:) if type_vars.empty?
|
|
104
|
+
|
|
105
|
+
Symbol::ImplementationTemplate.new(
|
|
106
|
+
interface: Symbol.type_ref_from_qualified_name(constraint.interface),
|
|
107
|
+
type: constraint.type,
|
|
108
|
+
type_params: type_vars.map { Type.var(it) },
|
|
109
|
+
constraints: union_constraints(constraint, type_vars, concrete),
|
|
110
|
+
functions: { 'show' => show_fn },
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def variant_case(variant, index_map, concrete, registry)
|
|
115
|
+
vars = (0...variant.args.length).map { |i| "a#{i}" }
|
|
116
|
+
name = variant.qualified_name.split('.').last
|
|
117
|
+
pattern = [:constructor, variant.qualified_name, vars]
|
|
118
|
+
|
|
119
|
+
return [pattern, [name]] if vars.empty?
|
|
120
|
+
|
|
121
|
+
rendered = variant.args.each_with_index.map do |arg_type, i|
|
|
122
|
+
idx =
|
|
123
|
+
case arg_type
|
|
124
|
+
in Symbol::Variable(name: var_name) then index_map[var_name]
|
|
125
|
+
else
|
|
126
|
+
index_map.size + concrete.index(instantiate(arg_type, {}, registry))
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
shown(idx, [:var, vars[i]])
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
parts = [name, '('] + intersperse(rendered, ', ') + [')']
|
|
133
|
+
|
|
134
|
+
[pattern, [concat(parts)]]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def intersperse(items, separator)
|
|
138
|
+
items.flat_map { [separator, it] }.drop(1)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def derive_struct(constraint, struct_sym, type_args, registry, lookup, entry_name)
|
|
142
|
+
fields = struct_fields(struct_sym, type_args, registry)
|
|
143
|
+
name = struct_sym.qualified_name.split('.').last
|
|
144
|
+
|
|
145
|
+
resolve_field_deps(fields.map { |_, type| type }, lookup, constraint.origin)
|
|
146
|
+
.and_then do |deps|
|
|
147
|
+
rendered = fields.each_with_index.map { |(field, _), idx|
|
|
148
|
+
[:call, [:stdlib_fn, 'String.concat'], [[:list, [
|
|
149
|
+
"#{field}: ",
|
|
150
|
+
shown(idx, [:access, [:var, 'value'], field.to_s]),
|
|
151
|
+
]]]]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
parts = [name, ' { '] + intersperse(rendered, ', ') + [' }']
|
|
155
|
+
|
|
156
|
+
Ok[record_impl(constraint, concat(parts), deps)]
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def derive_record(constraint, fields, lookup)
|
|
161
|
+
resolve_field_deps(fields.values, lookup, constraint.origin)
|
|
162
|
+
.and_then do |deps|
|
|
163
|
+
rendered = fields.keys.each_with_index.map { |field, idx|
|
|
164
|
+
[:call, [:stdlib_fn, 'String.concat'], [[:list, [
|
|
165
|
+
"#{field}: ",
|
|
166
|
+
shown(idx, [:access, [:var, 'value'], field.to_s]),
|
|
167
|
+
]]]]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
parts = ['{ '] + intersperse(rendered, ', ') + [' }']
|
|
171
|
+
|
|
172
|
+
Ok[record_impl(constraint, concat(parts), deps)]
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def record_impl(constraint, body, deps)
|
|
177
|
+
Symbol::DerivedFunction
|
|
178
|
+
.new(params: ['value'], body:)
|
|
179
|
+
.then { implementation(constraint, { 'show' => it }, deps:) }
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -3,6 +3,7 @@ require_relative './deriving/eq.rb'
|
|
|
3
3
|
require_relative './deriving/decodable.rb'
|
|
4
4
|
require_relative './deriving/encodable.rb'
|
|
5
5
|
require_relative './deriving/sql_mapper.rb'
|
|
6
|
+
require_relative './deriving/show.rb'
|
|
6
7
|
|
|
7
8
|
module Jade
|
|
8
9
|
module Frontend
|
|
@@ -11,7 +12,7 @@ module Jade
|
|
|
11
12
|
module Deriving
|
|
12
13
|
extend self
|
|
13
14
|
|
|
14
|
-
DERIVERS = [Eq, Decodable, Encodable, SqlMapper]
|
|
15
|
+
DERIVERS = [Eq, Show, Decodable, Encodable, SqlMapper]
|
|
15
16
|
|
|
16
17
|
def derivable?(interface)
|
|
17
18
|
DERIVERS.any? { it.supports?(interface) }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module TypeChecking
|
|
4
|
+
module Error
|
|
5
|
+
class PortNotEncodable < Jade::Error
|
|
6
|
+
attr_reader :port_name, :position, :type
|
|
7
|
+
|
|
8
|
+
def initialize(entry, span, port_name:, position:, type:)
|
|
9
|
+
@port_name = port_name
|
|
10
|
+
@position = position
|
|
11
|
+
@type = type
|
|
12
|
+
super(entry:, span:)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def message
|
|
16
|
+
"Port `#{@port_name}` cannot encode argument #{@position} (`#{@type}`): " \
|
|
17
|
+
"no Encodable instance"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def label
|
|
21
|
+
"no Encodable instance for `#{@type}`"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def notes
|
|
25
|
+
[
|
|
26
|
+
Jade::Diagnostics::Annotation[
|
|
27
|
+
:help,
|
|
28
|
+
"implement Encodable for `#{@type}` so it can be encoded on the " \
|
|
29
|
+
"way out, or declare the argument as `Decode.Value` to hand " \
|
|
30
|
+
"Ruby the value untouched",
|
|
31
|
+
],
|
|
32
|
+
]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|