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
|
@@ -146,7 +146,8 @@ module Jade
|
|
|
146
146
|
|
|
147
147
|
in AST::VariableReference | AST::ConstructorReference | AST::TypeDeclaration |
|
|
148
148
|
AST::ImportDeclaration | AST::Literal | AST::CharLiteral | AST::RecordAccessSugar | AST::InteropImportDeclaration |
|
|
149
|
-
AST::StructDeclaration | AST::QualifiedAccess | AST::Placeholder | AST::InterfaceDeclaration
|
|
149
|
+
AST::StructDeclaration | AST::QualifiedAccess | AST::Placeholder | AST::InterfaceDeclaration |
|
|
150
|
+
AST::RecordUpdateSugar
|
|
150
151
|
|
|
151
152
|
node
|
|
152
153
|
|
|
@@ -164,7 +165,7 @@ module Jade
|
|
|
164
165
|
flatten(left) + [operator] + flatten(right)
|
|
165
166
|
|
|
166
167
|
else
|
|
167
|
-
[ast]
|
|
168
|
+
[fix(ast)]
|
|
168
169
|
end
|
|
169
170
|
end
|
|
170
171
|
|
|
@@ -47,8 +47,8 @@ module Jade
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def fn_type_to_interop(interop_mod_name, function_node, symbol, entry, registry)
|
|
50
|
-
lifted_errors =
|
|
51
|
-
.validate(
|
|
50
|
+
lifted_errors = [symbol.return_type, *symbol.params]
|
|
51
|
+
.flat_map { Interop::Lowering.validate(it, registry, entry) }
|
|
52
52
|
.map { Error::TypeNotLowerable.new(entry, function_node.range, message: it.message) }
|
|
53
53
|
|
|
54
54
|
Symbol
|
|
@@ -57,7 +57,8 @@ module Jade
|
|
|
57
57
|
symbol.params,
|
|
58
58
|
symbol.return_type,
|
|
59
59
|
interop_mod_name.name,
|
|
60
|
-
constraints: implicit_decodable_constraints(symbol.return_type)
|
|
60
|
+
constraints: implicit_decodable_constraints(symbol.return_type) +
|
|
61
|
+
implicit_encodable_constraints(symbol.params),
|
|
61
62
|
)
|
|
62
63
|
.then { [it, lifted_errors] }
|
|
63
64
|
end
|
|
@@ -78,6 +79,15 @@ module Jade
|
|
|
78
79
|
end
|
|
79
80
|
end
|
|
80
81
|
|
|
82
|
+
# Arguments travel the other way, so a free variable in a parameter
|
|
83
|
+
# needs the caller's Encodable instance rather than its Decodable one.
|
|
84
|
+
def implicit_encodable_constraints(params)
|
|
85
|
+
params
|
|
86
|
+
.flat_map { collect_var_names(it) }
|
|
87
|
+
.uniq
|
|
88
|
+
.map { ['Encode.Encodable', it] }
|
|
89
|
+
end
|
|
90
|
+
|
|
81
91
|
def collect_var_names(type_sym)
|
|
82
92
|
case type_sym
|
|
83
93
|
in Symbol::Variable(name:)
|
|
@@ -39,7 +39,7 @@ module Jade
|
|
|
39
39
|
with(rows: rows + other.rows)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
def missing_patterns(env
|
|
42
|
+
def missing_patterns(env)
|
|
43
43
|
if types.empty?
|
|
44
44
|
return rows.empty? ? Matrix[[[]], types] : Matrix.empty
|
|
45
45
|
end
|
|
@@ -49,37 +49,72 @@ module Jade
|
|
|
49
49
|
return Matrix.empty if never?(type)
|
|
50
50
|
return Matrix.wildcard(types) if rows.empty?
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
matrix = default
|
|
54
|
-
.missing_patterns(env, seen_recursive_types)
|
|
52
|
+
heads = head_constructors
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
if infinite?(type) || type_var?(type)
|
|
55
|
+
unmatched_column(env)
|
|
58
56
|
|
|
59
57
|
elsif expandable?(type, env)
|
|
60
|
-
expand(env).missing_patterns(env
|
|
58
|
+
expand(env).missing_patterns(env)
|
|
59
|
+
|
|
60
|
+
elsif heads.empty?
|
|
61
|
+
unmatched_column(env)
|
|
61
62
|
|
|
62
63
|
else
|
|
63
|
-
new_seen = seen_recursive_types | ::Set[type]
|
|
64
64
|
constructors_of(type, env)
|
|
65
65
|
.reduce(Matrix.empty.with(types:)) do |acc, constructor|
|
|
66
|
-
|
|
67
|
-
.
|
|
68
|
-
|
|
69
|
-
missing
|
|
70
|
-
.map do |row|
|
|
71
|
-
new_args = row.take(constructor.args.size)
|
|
72
|
-
tail = row.drop(constructor.args.size)
|
|
73
|
-
|
|
74
|
-
[Constructor[constructor.name, new_args]] + tail
|
|
75
|
-
end
|
|
76
|
-
.then { acc.concat(it) }
|
|
66
|
+
witnesses(constructor, env, heads.include?(constructor.name))
|
|
67
|
+
.then { acc.concat(it) }
|
|
77
68
|
end
|
|
78
69
|
end
|
|
79
70
|
end
|
|
80
71
|
|
|
81
72
|
protected
|
|
82
73
|
|
|
74
|
+
# Nothing in this column narrows anything — a type with no constructors
|
|
75
|
+
# to enumerate, or one every row left open. Whatever is missing lives in
|
|
76
|
+
# the columns after it.
|
|
77
|
+
def unmatched_column(env)
|
|
78
|
+
default
|
|
79
|
+
.missing_patterns(env)
|
|
80
|
+
.map { [Wildcard[]] + it }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# A constructor the first column matches is explored by specializing on
|
|
84
|
+
# it. One it never matches can only be missing as a whole, so its
|
|
85
|
+
# witnesses come from the rows that would have covered it — the ones
|
|
86
|
+
# headed by a wildcard. Recursing there instead of into the constructor
|
|
87
|
+
# is what keeps a recursive type from expanding forever.
|
|
88
|
+
def witnesses(constructor, env, matched)
|
|
89
|
+
return Matrix.empty if constructor.args.any? { never?(it) }
|
|
90
|
+
|
|
91
|
+
arity = constructor.args.size
|
|
92
|
+
|
|
93
|
+
if matched
|
|
94
|
+
specialize(constructor)
|
|
95
|
+
.missing_patterns(env)
|
|
96
|
+
.map { [Constructor[constructor.name, it.take(arity)]] + it.drop(arity) }
|
|
97
|
+
|
|
98
|
+
else
|
|
99
|
+
default
|
|
100
|
+
.missing_patterns(env)
|
|
101
|
+
.map { [Constructor[constructor.name, Array.new(arity) { Wildcard[] }]] + it }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def head_constructors
|
|
106
|
+
rows.filter_map { head_constructor(it.first) }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def head_constructor(pattern)
|
|
110
|
+
case pattern
|
|
111
|
+
in Constructor(constructor: name) then name
|
|
112
|
+
in Literal(value: true) then 'Basics.True'
|
|
113
|
+
in Literal(value: false) then 'Basics.False'
|
|
114
|
+
in Literal | Record | Wildcard then nil
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
83
118
|
def expandable?(type, env)
|
|
84
119
|
case type
|
|
85
120
|
in Type::AnonymousRecord
|
|
@@ -125,9 +160,8 @@ module Jade
|
|
|
125
160
|
TypeChecking::ConstructorDef['Basics.True', 'Basics.Bool', []],
|
|
126
161
|
TypeChecking::ConstructorDef['Basics.False', 'Basics.Bool', []],
|
|
127
162
|
]
|
|
128
|
-
in Type::Application(constructor: Type::Constructor(name: /^Tuple\.Tuple
|
|
129
|
-
|
|
130
|
-
[TypeChecking::Definition.constructor(name, name, Array.new(n) { env.fresh })]
|
|
163
|
+
in Type::Application(constructor: Type::Constructor(name: /^Tuple\.Tuple[2-4]$/ => name), args:)
|
|
164
|
+
[TypeChecking::Definition.constructor(name, name, args)]
|
|
131
165
|
|
|
132
166
|
in Type::Application(constructor: Type::Constructor(name: 'List.List'), args: [elem_type])
|
|
133
167
|
[
|
|
@@ -181,7 +215,7 @@ module Jade
|
|
|
181
215
|
end
|
|
182
216
|
end
|
|
183
217
|
.then { with(rows: it) }
|
|
184
|
-
.then { it.with(types: constructor.args) }
|
|
218
|
+
.then { it.with(types: constructor.args + types.drop(1)) }
|
|
185
219
|
end
|
|
186
220
|
|
|
187
221
|
def default
|
|
@@ -17,7 +17,7 @@ module Jade
|
|
|
17
17
|
entry.name,
|
|
18
18
|
node.range,
|
|
19
19
|
name:,
|
|
20
|
-
|
|
20
|
+
hint: constructor_hint(name, entry, registry),
|
|
21
21
|
candidates: scope.bindings.keys.select { it.match?(/\A[A-Z]/) },
|
|
22
22
|
))
|
|
23
23
|
.then do
|
|
@@ -49,14 +49,40 @@ module Jade
|
|
|
49
49
|
&.then { klass.new(entry.name, span, arity: it) }
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
# An imported module owning a constructor by this name is the whole
|
|
53
|
+
# story: either it keeps its constructors private, or it exposes them
|
|
54
|
+
# and the import here asked for the type alone.
|
|
55
|
+
def constructor_hint(name, entry, registry)
|
|
56
|
+
entry
|
|
57
|
+
.imports
|
|
58
|
+
.filter_map { hint_for(it.module_name, name, registry) }
|
|
59
|
+
.first
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def hint_for(module_name, name, registry)
|
|
63
|
+
target = registry.get(module_name)
|
|
64
|
+
return nil if target.nil? || Stdlib.is_stdlib?(target)
|
|
65
|
+
|
|
66
|
+
type_name = owning_type(target, name)
|
|
67
|
+
return nil unless type_name && target.exposed_type(type_name)
|
|
55
68
|
|
|
56
|
-
|
|
57
|
-
|
|
69
|
+
exposes_constructor?(target, type_name, name)
|
|
70
|
+
.then { it ? :import : :expose }
|
|
71
|
+
.then { Error::ConstructorNotFound::Hint[module_name, type_name, it] }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def owning_type(target, name)
|
|
75
|
+
target
|
|
76
|
+
.defined_types
|
|
77
|
+
.find { |_, sym| sym.constructor_refs.any? { it.name == name } }
|
|
78
|
+
&.first
|
|
79
|
+
end
|
|
58
80
|
|
|
59
|
-
|
|
81
|
+
def exposes_constructor?(target, type_name, name)
|
|
82
|
+
target
|
|
83
|
+
.exposed_type_variants(type_name)
|
|
84
|
+
.to_a
|
|
85
|
+
.any? { it.name == name }
|
|
60
86
|
end
|
|
61
87
|
end
|
|
62
88
|
end
|
|
@@ -3,21 +3,36 @@ module Jade
|
|
|
3
3
|
module SemanticAnalysis
|
|
4
4
|
module Error
|
|
5
5
|
class ConstructorNotFound < Jade::Error
|
|
6
|
+
# `fix` is where the `(..)` is missing: `:expose` when the module
|
|
7
|
+
# that owns the type keeps its constructors to itself, `:import`
|
|
8
|
+
# when it exposes them and this module's import didn't ask for them.
|
|
9
|
+
Hint = Data.define(:module_name, :type_name, :fix)
|
|
10
|
+
|
|
6
11
|
attr_reader :candidates
|
|
7
12
|
|
|
8
|
-
def initialize(entry, span, name:,
|
|
13
|
+
def initialize(entry, span, name:, hint: nil, candidates: [])
|
|
9
14
|
@name = name
|
|
10
|
-
@
|
|
15
|
+
@hint = hint
|
|
11
16
|
@candidates = candidates
|
|
12
17
|
super(entry:, span:)
|
|
13
18
|
end
|
|
14
19
|
|
|
15
20
|
def message
|
|
16
21
|
base = "I cannot find a `#{@name}` constructor"
|
|
17
|
-
return base unless @exposed_type_module
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
case @hint
|
|
24
|
+
in nil
|
|
25
|
+
base
|
|
26
|
+
|
|
27
|
+
in Hint(module_name:, type_name:, fix: :expose)
|
|
28
|
+
"#{base}. The type `#{type_name}` is exposed by `#{module_name}` but its " \
|
|
29
|
+
"constructor is private — add `#{type_name}(..)` to that module's " \
|
|
30
|
+
"`exposing` list."
|
|
31
|
+
|
|
32
|
+
in Hint(module_name:, type_name:, fix: :import)
|
|
33
|
+
"#{base}. `#{module_name}` exposes it — add `#{type_name}(..)` to this " \
|
|
34
|
+
"module's `import #{module_name} exposing (...)`."
|
|
35
|
+
end
|
|
21
36
|
end
|
|
22
37
|
|
|
23
38
|
def label
|
|
@@ -3,11 +3,12 @@ module Jade
|
|
|
3
3
|
module SemanticAnalysis
|
|
4
4
|
module Error
|
|
5
5
|
class VariableNotFound < Jade::Error
|
|
6
|
-
attr_reader :causes
|
|
6
|
+
attr_reader :causes, :candidates
|
|
7
7
|
|
|
8
|
-
def initialize(entry, span, name:, causes: [])
|
|
8
|
+
def initialize(entry, span, name:, causes: [], candidates: [])
|
|
9
9
|
@name = name
|
|
10
10
|
@causes = causes
|
|
11
|
+
@candidates = candidates
|
|
11
12
|
super(entry:, span:)
|
|
12
13
|
end
|
|
13
14
|
|
|
@@ -18,6 +19,12 @@ module Jade
|
|
|
18
19
|
def label
|
|
19
20
|
"not found"
|
|
20
21
|
end
|
|
22
|
+
|
|
23
|
+
# Qualified, like `candidates`: bare `fold_left` against `fold` is
|
|
24
|
+
# too short to clear the spell checker's threshold.
|
|
25
|
+
def queried_name
|
|
26
|
+
@name
|
|
27
|
+
end
|
|
21
28
|
end
|
|
22
29
|
end
|
|
23
30
|
end
|
|
@@ -55,13 +55,14 @@ module Jade
|
|
|
55
55
|
import = entry
|
|
56
56
|
.imports
|
|
57
57
|
.find { it.alias == module_name }
|
|
58
|
+
imported = import&.then { registry.get(it.module_name) }
|
|
58
59
|
|
|
59
60
|
if import.nil?
|
|
60
61
|
Error::ModuleNotFound
|
|
61
62
|
.new(entry.name, node.target.range, name: import)
|
|
62
63
|
|
|
63
64
|
else
|
|
64
|
-
case
|
|
65
|
+
case imported.exposed_value(name.name)
|
|
65
66
|
in nil
|
|
66
67
|
Error::ValueNotExposed
|
|
67
68
|
.new(entry.name, name.range, module_name:, name: name.name)
|
|
@@ -77,10 +78,19 @@ module Jade
|
|
|
77
78
|
node.range,
|
|
78
79
|
name: [module_name, name.name].join('.'),
|
|
79
80
|
causes: [it],
|
|
81
|
+
candidates: exposed_value_names(imported, module_name),
|
|
80
82
|
)
|
|
81
83
|
end
|
|
82
84
|
.then { Err[it] }
|
|
83
85
|
end
|
|
86
|
+
|
|
87
|
+
def exposed_value_names(imported, module_name)
|
|
88
|
+
return [] unless imported
|
|
89
|
+
|
|
90
|
+
imported
|
|
91
|
+
.exposes
|
|
92
|
+
.filter_map { "#{module_name}.#{it.name}" if it.is_a?(Symbol::ValueRef) }
|
|
93
|
+
end
|
|
84
94
|
end
|
|
85
95
|
end
|
|
86
96
|
end
|
|
@@ -11,13 +11,23 @@ module Jade
|
|
|
11
11
|
|
|
12
12
|
def supports?(interface) = interface == INTERFACE
|
|
13
13
|
|
|
14
|
+
# One decoder combinator per structural type, taking a decoder per
|
|
15
|
+
# element. `Tuple2(a, b)` is `Decode.tuple(dec_a, dec_b)` the same
|
|
16
|
+
# way `List(a)` is `Decode.list(dec_a)`.
|
|
17
|
+
STRUCTURAL = {
|
|
18
|
+
'List.List' => 'Decode.list',
|
|
19
|
+
'Maybe.Maybe' => 'Decode.nullable',
|
|
20
|
+
'Set.Set' => 'Decode.set',
|
|
21
|
+
'Dict.Dict' => 'Decode.dict',
|
|
22
|
+
'Tuple.Tuple2' => 'Decode.tuple',
|
|
23
|
+
'Tuple.Tuple3' => 'Decode.tuple3',
|
|
24
|
+
'Tuple.Tuple4' => 'Decode.tuple4',
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
14
27
|
def derive(constraint, registry, entry_name, &lookup)
|
|
15
28
|
case constraint.type
|
|
16
|
-
in Type::Application(constructor: Type::Constructor(name:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
in Type::Application(constructor: Type::Constructor(name: 'Maybe.Maybe'), args: [inner])
|
|
20
|
-
derive_unary(constraint, inner, 'Decode.nullable', lookup, entry_name)
|
|
29
|
+
in Type::Application(constructor: Type::Constructor(name:), args:) if STRUCTURAL.key?(name)
|
|
30
|
+
derive_structural(constraint, args, STRUCTURAL.fetch(name), lookup, entry_name)
|
|
21
31
|
|
|
22
32
|
in Type::Application(constructor: Type::Constructor(name:), args:)
|
|
23
33
|
resolved_sym = Symbol
|
|
@@ -56,32 +66,30 @@ module Jade
|
|
|
56
66
|
]
|
|
57
67
|
end
|
|
58
68
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
[:stdlib_fn, stdlib_fn],
|
|
65
|
-
[
|
|
66
|
-
[:impl_arg, 0, 'decoder'],
|
|
67
|
-
],
|
|
68
|
-
]
|
|
69
|
+
def derive_structural(constraint, inner_types, stdlib_fn, lookup, entry_name)
|
|
70
|
+
body = [:call,
|
|
71
|
+
[:stdlib_fn, stdlib_fn],
|
|
72
|
+
inner_types.each_index.map { [:impl_arg, it, 'decoder'] },
|
|
73
|
+
]
|
|
69
74
|
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
inner_types
|
|
76
|
+
.map { Type.constraint(INTERFACE, it, nil) }
|
|
77
|
+
.map { resolve_dep(it, lookup, entry_name) }
|
|
78
|
+
.then { Results.sequence(it) }
|
|
79
|
+
.map { implementation(constraint, body, it) }
|
|
72
80
|
end
|
|
73
81
|
|
|
74
82
|
def derive_struct(constraint, struct_sym, type_args, registry, lookup, entry_name)
|
|
75
83
|
fields = struct_fields(struct_sym, type_args, registry)
|
|
76
84
|
|
|
77
|
-
[:
|
|
85
|
+
[:struct_class, struct_sym.qualified_name]
|
|
78
86
|
.then { derive_record(constraint, fields, it, lookup, entry_name) }
|
|
79
87
|
end
|
|
80
88
|
|
|
81
89
|
def derive_anonymous_record(constraint, fields, lookup, entry_name)
|
|
82
90
|
keys = fields.keys.map(&:to_s)
|
|
83
91
|
|
|
84
|
-
[:
|
|
92
|
+
[:anon_record_class, keys]
|
|
85
93
|
.then { derive_record(constraint, fields.to_a, it, lookup, entry_name) }
|
|
86
94
|
end
|
|
87
95
|
|
|
@@ -113,19 +121,14 @@ module Jade
|
|
|
113
121
|
end
|
|
114
122
|
|
|
115
123
|
def record_body(fields, constructor_ref)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
[:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
],
|
|
125
|
-
]
|
|
126
|
-
|
|
127
|
-
[:call, [:stdlib_fn, 'Decode.and_map'], [acc, field_decoder]]
|
|
128
|
-
end
|
|
124
|
+
[:call,
|
|
125
|
+
[:stdlib_fn, 'Decode.record'],
|
|
126
|
+
[
|
|
127
|
+
[:list, fields.map { |name, _| name.to_s }],
|
|
128
|
+
[:list, fields.each_index.map { [:impl_arg, it, 'decoder'] }],
|
|
129
|
+
constructor_ref,
|
|
130
|
+
],
|
|
131
|
+
]
|
|
129
132
|
end
|
|
130
133
|
|
|
131
134
|
# Free-var inner constraints fall back to the constraint marker
|
|
@@ -13,13 +13,23 @@ module Jade
|
|
|
13
13
|
interface == INTERFACE
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
# One encoder combinator per structural type, taking an encoder per
|
|
17
|
+
# element plus the value itself. The name is what the emitted
|
|
18
|
+
# encoder binds its argument to.
|
|
19
|
+
STRUCTURAL = {
|
|
20
|
+
'List.List' => ['Encode.list', 'items'],
|
|
21
|
+
'Maybe.Maybe' => ['Encode.nullable', 'maybe'],
|
|
22
|
+
'Set.Set' => ['Encode.set', 'set'],
|
|
23
|
+
'Dict.Dict' => ['Encode.dict', 'dict'],
|
|
24
|
+
'Tuple.Tuple2' => ['Encode.tuple', 'pair'],
|
|
25
|
+
'Tuple.Tuple3' => ['Encode.tuple3', 'triple'],
|
|
26
|
+
'Tuple.Tuple4' => ['Encode.tuple4', 'quad'],
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
16
29
|
def derive(constraint, registry, entry_name, &lookup)
|
|
17
30
|
case constraint.type
|
|
18
|
-
in Type::Application(constructor: Type::Constructor(name:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
in Type::Application(constructor: Type::Constructor(name: 'Maybe.Maybe'), args: [inner])
|
|
22
|
-
derive_nullable(constraint, inner, lookup, entry_name)
|
|
31
|
+
in Type::Application(constructor: Type::Constructor(name:), args:) if STRUCTURAL.key?(name)
|
|
32
|
+
derive_structural(constraint, args, *STRUCTURAL.fetch(name), lookup, entry_name)
|
|
23
33
|
|
|
24
34
|
in Type::Application(constructor: Type::Constructor(name:), args:)
|
|
25
35
|
resolved_sym = Symbol
|
|
@@ -55,36 +65,17 @@ module Jade
|
|
|
55
65
|
]
|
|
56
66
|
end
|
|
57
67
|
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
[:stdlib_fn, 'Encode.list'],
|
|
64
|
-
[
|
|
65
|
-
[:impl_arg, 0, 'encoder'],
|
|
66
|
-
[:var, 'items'],
|
|
67
|
-
],
|
|
68
|
-
]
|
|
69
|
-
|
|
70
|
-
Ok[implementation(constraint, params: ['items'], body:, deps: [dep_impl])]
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def derive_nullable(constraint, inner_type, lookup, entry_name)
|
|
75
|
-
dep = Type.constraint(INTERFACE, inner_type, nil)
|
|
76
|
-
|
|
77
|
-
lookup.call(dep).and_then do |dep_impl|
|
|
78
|
-
body = [:call,
|
|
79
|
-
[:stdlib_fn, 'Encode.nullable'],
|
|
80
|
-
[
|
|
81
|
-
[:impl_arg, 0, 'encoder'],
|
|
82
|
-
[:var, 'maybe'],
|
|
83
|
-
],
|
|
84
|
-
]
|
|
68
|
+
def derive_structural(constraint, inner_types, stdlib_fn, param, lookup, entry_name)
|
|
69
|
+
body = [:call,
|
|
70
|
+
[:stdlib_fn, stdlib_fn],
|
|
71
|
+
inner_types.each_index.map { [:impl_arg, it, 'encoder'] } + [[:var, param]],
|
|
72
|
+
]
|
|
85
73
|
|
|
86
|
-
|
|
87
|
-
|
|
74
|
+
inner_types
|
|
75
|
+
.map { Type.constraint(INTERFACE, it, nil) }
|
|
76
|
+
.map { lookup.call(it) }
|
|
77
|
+
.then { Results.sequence(it) }
|
|
78
|
+
.map { implementation(constraint, params: [param], body:, deps: it) }
|
|
88
79
|
end
|
|
89
80
|
|
|
90
81
|
def derive_nullary_union(constraint, union_sym, registry)
|
|
@@ -106,27 +97,18 @@ module Jade
|
|
|
106
97
|
field_deps = fields
|
|
107
98
|
.map { |_, field_type| Type.constraint(INTERFACE, field_type, nil) }
|
|
108
99
|
|
|
109
|
-
|
|
100
|
+
body = fields
|
|
110
101
|
.each_with_index
|
|
111
102
|
.map do |(field_name, _), idx|
|
|
112
|
-
[
|
|
113
|
-
|
|
114
|
-
[
|
|
115
|
-
|
|
116
|
-
[:
|
|
117
|
-
[:impl_arg, idx, 'encoder'],
|
|
118
|
-
[[:access, [:var, 'rec'], field_name.to_s]],
|
|
119
|
-
],
|
|
103
|
+
[
|
|
104
|
+
field_name.to_s,
|
|
105
|
+
[:call,
|
|
106
|
+
[:impl_arg, idx, 'encoder'],
|
|
107
|
+
[[:access, [:var, 'rec'], field_name.to_s]],
|
|
120
108
|
],
|
|
121
109
|
]
|
|
122
110
|
end
|
|
123
|
-
|
|
124
|
-
body = [:call,
|
|
125
|
-
[:stdlib_fn, 'Encode.object'],
|
|
126
|
-
[
|
|
127
|
-
[:list, pair_irs],
|
|
128
|
-
],
|
|
129
|
-
]
|
|
111
|
+
.then { [:hash, it] }
|
|
130
112
|
|
|
131
113
|
field_deps
|
|
132
114
|
.map { lookup.call(it) }
|