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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +164 -0
  3. data/CHANGELOG.md +215 -1
  4. data/README.md +17 -12
  5. data/docs/interop.md +208 -0
  6. data/docs/json.md +163 -0
  7. data/docs/lsp.md +105 -0
  8. data/docs/stdlib.md +69 -0
  9. data/docs/syntax.md +458 -0
  10. data/docs/testing.md +70 -0
  11. data/lib/jade/api.rb +244 -0
  12. data/lib/jade/cli/check.rb +97 -0
  13. data/lib/jade/cli/q.rb +47 -1
  14. data/lib/jade/cli.rb +4 -2
  15. data/lib/jade/codegen/emitter.rb +11 -2
  16. data/lib/jade/codegen/function_call.rb +1 -1
  17. data/lib/jade/codegen/helpers.rb +6 -0
  18. data/lib/jade/codegen/inlines.rb +18 -0
  19. data/lib/jade/codegen/{port_decoder.rb → port_codec.rb} +27 -15
  20. data/lib/jade/codegen.rb +2 -2
  21. data/lib/jade/debug.rb +59 -0
  22. data/lib/jade/decode.rb +332 -212
  23. data/lib/jade/diagnostics/renderer.rb +16 -5
  24. data/lib/jade/frontend/fixity_fixer.rb +3 -2
  25. data/lib/jade/frontend/forward_declaration/interop_import_declaration.rb +13 -3
  26. data/lib/jade/frontend/pattern_analysis/matrix.rb +57 -23
  27. data/lib/jade/frontend/semantic_analysis/constructor_reference.rb +33 -7
  28. data/lib/jade/frontend/semantic_analysis/error/constructor_not_found.rb +20 -5
  29. data/lib/jade/frontend/semantic_analysis/error/variable_not_found.rb +9 -2
  30. data/lib/jade/frontend/semantic_analysis/member_access.rb +11 -1
  31. data/lib/jade/frontend/type_checking/constraints/deriving/decodable.rb +35 -32
  32. data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +32 -50
  33. data/lib/jade/frontend/type_checking/constraints/deriving/eq.rb +51 -175
  34. data/lib/jade/frontend/type_checking/constraints/deriving/helpers.rb +133 -0
  35. data/lib/jade/frontend/type_checking/constraints/deriving/show.rb +186 -0
  36. data/lib/jade/frontend/type_checking/constraints/deriving.rb +2 -1
  37. data/lib/jade/frontend/type_checking/error/port_not_encodable.rb +38 -0
  38. data/lib/jade/frontend/type_checking/port_resolution.rb +64 -16
  39. data/lib/jade/interop/boundary.rb +2 -3
  40. data/lib/jade/interop/runtime.rb +10 -2
  41. data/lib/jade/lsp/converters.rb +2 -15
  42. data/lib/jade/lsp/snippets.rb +21 -3
  43. data/lib/jade/parsing/error.rb +19 -0
  44. data/lib/jade/runtime.rb +1 -0
  45. data/lib/jade/signature.rb +47 -0
  46. data/lib/jade/stdlib/calendar.rb +20 -19
  47. data/lib/jade/stdlib/clock.rb +7 -13
  48. data/lib/jade/stdlib/debug.rb +12 -0
  49. data/lib/jade/stdlib/decimal.rb +3 -16
  50. data/lib/jade/stdlib/decode.rb +57 -12
  51. data/lib/jade/stdlib/encode.rb +26 -0
  52. data/lib/jade/stdlib/intrinsics.rb +12 -1
  53. data/lib/jade/stdlib/result.rb +1 -1
  54. data/lib/jade/stdlib/show.rb +40 -0
  55. data/lib/jade/stdlib/text.rb +131 -0
  56. data/lib/jade/stdlib.rb +5 -2
  57. data/lib/jade/symbol/interop_function.rb +3 -1
  58. data/lib/jade/symbol.rb +2 -2
  59. data/lib/jade/task.rb +2 -3
  60. data/lib/jade/version.rb +1 -1
  61. 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
- resolve(constraint, registry, entry_name, lookup)
15
+ resolve_constraint(constraint, registry, entry_name, lookup)
16
16
  end
17
17
 
18
18
  private
19
19
 
20
- def resolve(constraint, registry, entry_name, lookup)
21
- case [constraint.interface, constraint.type]
22
- in [INTERFACE, Type::Application(constructor:, args:)]
23
- registry
24
- .implementations[[constraint.interface, constructor.name]]
25
- .then do
26
- it \
27
- ? Ok[it]
28
- : derive_for_type(constraint, constructor, args, registry, lookup, entry_name)
29
- end
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
- case impl
33
- in Symbol::ImplementationTemplate
34
- deps = dependencies_of(impl, args)
35
- resolved_deps = deps.filter_map { |dep|
36
- next if dep.type in Type::Var
37
- lookup.call(dep).on_err { return Err[it] } => Ok[resolved]
38
- resolved
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::Implementation.new(
42
- module_name: nil,
43
- interface: Symbol.type_ref_from_qualified_name(constraint.interface),
44
- type: constraint.type,
45
- type_params: args,
46
- constraints: deps,
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
- else
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 derive_union_eq(constraint, symbol, registry, lookup)
125
- type_vars = symbol.type_params.map(&:name)
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
- eq_fn = Symbol::DerivedFunction.new(
133
- params: ["one", "other"],
134
- body: [:case,
135
- [:list, [[:var, "one"], [:var, "other"]]],
136
- cases + [[[:_], [false]]],
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, lookup, origin)
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
- case arg_type
177
-
178
- in Symbol::Variable(name:)
179
- idx = index_map[name]
180
-
181
- [:call,
182
- [:impl_arg, idx, "(==)"],
183
- [[:var, left_vars[i]], [:var, right_vars[i]]]
184
- ]
185
-
186
- else
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 derive_record_eq(constraint, fields, lookup)
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 derive_struct_eq(constraint, struct_sym, type_args, registry, lookup, entry_name)
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
- body = comparisons.empty? ? true : comparisons.reduce { |a, b| [:and, a, b] }
247
- eq_fn = Symbol::DerivedFunction.new(params: ['one', 'other'], body:)
248
-
249
- Symbol::Implementation.new(
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