jade-lang 0.7.0 → 0.9.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 +65 -0
- data/lib/jade/error.rb +5 -0
- data/lib/jade/extensions.rb +70 -0
- data/lib/jade/frontend/semantic_analysis/body.rb +29 -1
- data/lib/jade/frontend/semantic_analysis/error/duplicate_implementation.rb +39 -0
- data/lib/jade/frontend/semantic_analysis/error.rb +1 -0
- data/lib/jade/frontend/semantic_analysis/helper.rb +10 -0
- data/lib/jade/frontend/semantic_analysis/implementation.rb +0 -10
- data/lib/jade/frontend/type_checking/constraints/deriving.rb +5 -4
- data/lib/jade/frontend/type_checking/inference/function_call.rb +21 -1
- data/lib/jade/task.rb +74 -54
- data/lib/jade/version.rb +1 -1
- data/lib/jade.rb +1 -0
- metadata +4 -3
- data/lib/jade/frontend/type_checking/constraints/deriving/sql_mapper.rb +0 -130
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7b817b13aad1b314b6a8bf578f8a604246d19091712a77375376515732b6dcbc
|
|
4
|
+
data.tar.gz: 7be6b4091ac7c57c6befe21fb0ffbea302b9e33bd2940479b7c4227dd37cd1b6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9b758da80e8b9033dd6b5ce840188f7d69a8d482cf496a3731c4a4f7b5bcb90ddd0119e5b5eb6e9b1763bddffca4f3df49b6cba7d6a26d9304538efde7a8aa49
|
|
7
|
+
data.tar.gz: 22abc8374f37d7f9ff3c03f5f2a35861322f234227c3d0932e80c56e17bb49de5ce6ac67dc7c1f0b1896ce2b9a5c3562a2d8197f697f74fdd5d18e5db64094f6
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,71 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`Jade::Extensions`, where a gem hooks into compilation.** Two kinds, both
|
|
12
|
+
read-only: a *deriver* builds an implementation for an interface it owns,
|
|
13
|
+
and a *check* reads a call site and returns errors. Checks register against
|
|
14
|
+
a named phase — `:call` today — and receive the call's AST node alongside
|
|
15
|
+
its resolved argument types, because types alone cannot see a raw SQL
|
|
16
|
+
string's placeholders or a constant predicate. Only gems named in
|
|
17
|
+
`Extensions::ALLOWED` may register, so the compiler knows who extends it and
|
|
18
|
+
nothing about what they do.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **The `Sql.Assignable` deriver moves to jade-sql.** The deriver list named
|
|
23
|
+
jade-sql's interface outright, with a comment apologising for it; it now
|
|
24
|
+
holds only the built-ins and whatever an allowed gem registers.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **A long chain of `Task.and_then` no longer exhausts the Ruby stack.** Each
|
|
29
|
+
`and_then` ran the next task from inside the previous one's `run`, so depth
|
|
30
|
+
cost a stack frame and anything built by recursion, a batch loop or a retry,
|
|
31
|
+
died with `SystemStackError` somewhere past ten thousand links. `run` now
|
|
32
|
+
drives an explicit stack of continuations from one loop, so a chain is
|
|
33
|
+
bounded by memory: 500,000 links run where 100,000 used to fail.
|
|
34
|
+
|
|
35
|
+
- **Two implementations for the same head type is now an error.** An
|
|
36
|
+
implementation is registered under `[interface, head type]`, so a second
|
|
37
|
+
`implements Assignable(Box(Cols2, Val2))` overwrote
|
|
38
|
+
`implements Assignable(Box(Cols, Val))` and every call — including ones whose
|
|
39
|
+
types matched the first — dispatched to the second. It compiled clean and
|
|
40
|
+
died at run time in the implementation body, or worse, didn't. The second
|
|
41
|
+
declaration is now reported, with the first as a secondary label and a note
|
|
42
|
+
that type arguments do not select between implementations. Duplicates can
|
|
43
|
+
only arise within one module: the orphan rule and cycle detection between
|
|
44
|
+
them rule out the cross-module case.
|
|
45
|
+
|
|
46
|
+
## [0.8.0]
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- **A struct written to a table is checked against its columns.** jade-sql maps
|
|
51
|
+
a struct's fields onto columns by name, and the mapping derives, so nothing
|
|
52
|
+
compared the two — a field the table has no column for reached Postgres as
|
|
53
|
+
invalid SQL, and a field whose type disagreed with its column failed at
|
|
54
|
+
decode. Calls to `Sql.Mutation.insert`, `insert_all` and `update` now report
|
|
55
|
+
both at compile time, naming the field. Both types are concrete at the call
|
|
56
|
+
site, so this is a check rather than anything the type system has to carry.
|
|
57
|
+
A call through a generic helper of your own has neither type in hand and
|
|
58
|
+
stays unchecked. Inert without jade-sql, since nothing else declares those
|
|
59
|
+
functions.
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
|
|
63
|
+
- `Assignable` also derives for structs, naming one column per field in
|
|
64
|
+
declaration order. A field renamed to dodge a keyword (`type_`) maps back to
|
|
65
|
+
the column it came from. Generic structs derive at the type they are applied
|
|
66
|
+
to. Inert unless jade-sql is loaded.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- The derived jade-sql interface is now `Sql.Assignable`, was `Sql.SqlMapper`.
|
|
71
|
+
It is matched by name, so jade-sql must move to the new name in the same
|
|
72
|
+
release.
|
|
73
|
+
|
|
9
74
|
## [0.7.0]
|
|
10
75
|
|
|
11
76
|
### Added
|
data/lib/jade/error.rb
CHANGED
|
@@ -21,6 +21,10 @@ module Jade
|
|
|
21
21
|
[]
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
def secondary
|
|
25
|
+
[]
|
|
26
|
+
end
|
|
27
|
+
|
|
24
28
|
def queried_name
|
|
25
29
|
nil
|
|
26
30
|
end
|
|
@@ -39,6 +43,7 @@ module Jade
|
|
|
39
43
|
Jade::Diagnostics::Diagnostic.error(
|
|
40
44
|
message,
|
|
41
45
|
primary: Jade::Diagnostics::Label[source, span, label],
|
|
46
|
+
secondary: secondary.map { |sp, text| Jade::Diagnostics::Label[source, sp, text] },
|
|
42
47
|
annotations: notes + did_you_mean_notes,
|
|
43
48
|
)
|
|
44
49
|
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
# Where an extension gem hooks into compilation. Only gems named in ALLOWED
|
|
3
|
+
# may register, so jade knows who extends it and nothing about what they do.
|
|
4
|
+
module Extensions
|
|
5
|
+
extend self
|
|
6
|
+
|
|
7
|
+
ALLOWED = %w[jade-sql].freeze
|
|
8
|
+
PHASES = %i[call].freeze
|
|
9
|
+
|
|
10
|
+
class NotAllowed < StandardError
|
|
11
|
+
def initialize(gem_name)
|
|
12
|
+
super(
|
|
13
|
+
"#{gem_name} may not extend the compiler; " \
|
|
14
|
+
"allowed: #{ALLOWED.join(', ')}"
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class UnknownPhase < StandardError
|
|
20
|
+
def initialize(phase)
|
|
21
|
+
super("no such check phase #{phase.inspect}; known: #{PHASES.join(', ')}")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def register_deriver(gem_name, deriver)
|
|
26
|
+
allow!(gem_name)
|
|
27
|
+
@derivers = derivers | [deriver]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# A `:call` check answers `watches -> [qualified name]` and
|
|
31
|
+
# `check(context) -> [error]`. Watching by name keeps every other call in
|
|
32
|
+
# the program off the check's path entirely.
|
|
33
|
+
def register_check(gem_name, phase, check)
|
|
34
|
+
allow!(gem_name)
|
|
35
|
+
fail UnknownPhase.new(phase) unless PHASES.include?(phase)
|
|
36
|
+
|
|
37
|
+
@call_checks = check
|
|
38
|
+
.watches
|
|
39
|
+
.reduce(call_checks) { |acc, name| acc.merge(name => acc.fetch(name, []) | [check]) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def derivers = @derivers ||= []
|
|
43
|
+
|
|
44
|
+
def call_checks = @call_checks ||= {}
|
|
45
|
+
|
|
46
|
+
# The node travels with the types because a literal's value — a SQL string,
|
|
47
|
+
# a constant predicate — is not in them.
|
|
48
|
+
CallContext = Data.define(:name, :arg_types, :node, :registry, :entry_name, :span)
|
|
49
|
+
|
|
50
|
+
def check_call(name, arg_types, node, registry, entry_name, span)
|
|
51
|
+
call_checks[name].then do |watching|
|
|
52
|
+
next [] if watching.nil?
|
|
53
|
+
|
|
54
|
+
CallContext[name, arg_types, node, registry, entry_name, span]
|
|
55
|
+
.then { |ctx| watching.flat_map { it.check(ctx) } }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def reset!
|
|
60
|
+
@derivers = []
|
|
61
|
+
@call_checks = {}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def allow!(gem_name)
|
|
67
|
+
fail NotAllowed.new(gem_name) unless ALLOWED.include?(gem_name)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -24,9 +24,37 @@ module Jade
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
analyze_in_sequence(expressions, registry, scope, entry)
|
|
27
|
-
.add_errors(duplicate_errors)
|
|
27
|
+
.add_errors(duplicate_errors + duplicate_implementation_errors(expressions, entry))
|
|
28
28
|
.map_node { node.with(expressions: it) }
|
|
29
29
|
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def duplicate_implementation_errors(expressions, entry)
|
|
34
|
+
expressions
|
|
35
|
+
.select { it.is_a?(AST::Implementation) }
|
|
36
|
+
.group_by { implementation_key(it, entry) }
|
|
37
|
+
.reject { |key, impls| key.nil? || impls.size < 2 }
|
|
38
|
+
.flat_map do |(interface, type), (first, *rest)|
|
|
39
|
+
rest.map do |dup|
|
|
40
|
+
Error::DuplicateImplementation.new(
|
|
41
|
+
entry.name,
|
|
42
|
+
dup.range,
|
|
43
|
+
interface:,
|
|
44
|
+
type:,
|
|
45
|
+
first_span: first.range,
|
|
46
|
+
parameterized: dup.applied_type.args.any?,
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def implementation_key(node, entry)
|
|
53
|
+
interface = entry.lookup_type(node.interface)
|
|
54
|
+
type = lookup_applied_type(node.applied_type, entry)
|
|
55
|
+
|
|
56
|
+
[interface.qname, type.qname] if interface && type
|
|
57
|
+
end
|
|
30
58
|
end
|
|
31
59
|
end
|
|
32
60
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module SemanticAnalysis
|
|
4
|
+
module Error
|
|
5
|
+
class DuplicateImplementation < Jade::Error
|
|
6
|
+
def initialize(entry, span, interface:, type:, first_span:, parameterized:)
|
|
7
|
+
@interface = interface
|
|
8
|
+
@type = type
|
|
9
|
+
@first_span = first_span
|
|
10
|
+
@parameterized = parameterized
|
|
11
|
+
super(entry:, span:)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def message
|
|
15
|
+
"Duplicate implementation of #{@interface} for #{@type}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def label
|
|
19
|
+
"already implemented"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def secondary
|
|
23
|
+
[[@first_span, 'first implemented here']]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def notes
|
|
27
|
+
return [] unless @parameterized
|
|
28
|
+
|
|
29
|
+
[Jade::Diagnostics::Annotation[
|
|
30
|
+
:note,
|
|
31
|
+
"an implementation is chosen by the head type `#{@type}` alone — " \
|
|
32
|
+
'its type arguments do not select between implementations',
|
|
33
|
+
]]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -7,6 +7,7 @@ require 'jade/frontend/semantic_analysis/error/constructor_pattern_arity_mismatc
|
|
|
7
7
|
require 'jade/frontend/semantic_analysis/error/duplicate_field'
|
|
8
8
|
require 'jade/frontend/semantic_analysis/error/placeholder_not_allowed'
|
|
9
9
|
require 'jade/frontend/semantic_analysis/error/duplicate_function_declaration'
|
|
10
|
+
require 'jade/frontend/semantic_analysis/error/duplicate_implementation'
|
|
10
11
|
require 'jade/frontend/semantic_analysis/error/duplicate_record_field'
|
|
11
12
|
require 'jade/frontend/semantic_analysis/error/invalid_list_rest_pattern'
|
|
12
13
|
require 'jade/frontend/semantic_analysis/error/kwargs_on_non_constructor'
|
|
@@ -49,6 +49,16 @@ module Jade
|
|
|
49
49
|
Result[results.map(&:node), results.flat_map(&:errors), scope]
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
private def lookup_applied_type(applied_type, entry)
|
|
53
|
+
case applied_type.constructor
|
|
54
|
+
in AST::TypeName(type:)
|
|
55
|
+
entry.lookup_type(type)
|
|
56
|
+
in AST::QualifiedTypeName(path:)
|
|
57
|
+
*module_parts, type_name = path
|
|
58
|
+
entry.lookup_qualified_type(module_parts.join('.'), type_name)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
52
62
|
def analyze_duplicate_fields(fields, entry)
|
|
53
63
|
fields
|
|
54
64
|
.group_by(&:key)
|
|
@@ -73,16 +73,6 @@ module Jade
|
|
|
73
73
|
|
|
74
74
|
private
|
|
75
75
|
|
|
76
|
-
def lookup_applied_type(applied_type, entry)
|
|
77
|
-
case applied_type.constructor
|
|
78
|
-
in AST::TypeName(type:)
|
|
79
|
-
entry.lookup_type(type)
|
|
80
|
-
in AST::QualifiedTypeName(path:)
|
|
81
|
-
*module_parts, type_name = path
|
|
82
|
-
entry.lookup_qualified_type(module_parts.join('.'), type_name)
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
76
|
def local_type_name(applied_type)
|
|
87
77
|
case applied_type.constructor
|
|
88
78
|
in AST::TypeName(type:) then type
|
|
@@ -2,7 +2,6 @@ require_relative './deriving/helpers.rb'
|
|
|
2
2
|
require_relative './deriving/eq.rb'
|
|
3
3
|
require_relative './deriving/decodable.rb'
|
|
4
4
|
require_relative './deriving/encodable.rb'
|
|
5
|
-
require_relative './deriving/sql_mapper.rb'
|
|
6
5
|
require_relative './deriving/show.rb'
|
|
7
6
|
|
|
8
7
|
module Jade
|
|
@@ -12,14 +11,16 @@ module Jade
|
|
|
12
11
|
module Deriving
|
|
13
12
|
extend self
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
BUILTIN = [Eq, Show, Decodable, Encodable].freeze
|
|
15
|
+
|
|
16
|
+
def derivers = BUILTIN + Extensions.derivers
|
|
16
17
|
|
|
17
18
|
def derivable?(interface)
|
|
18
|
-
|
|
19
|
+
derivers.any? { it.supports?(interface) }
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def derive(constraint, registry, entry_name, &lookup)
|
|
22
|
-
|
|
23
|
+
derivers
|
|
23
24
|
.find { it.supports?(constraint.interface) }
|
|
24
25
|
.then { it.derive(constraint, registry, entry_name, &lookup) }
|
|
25
26
|
end
|
|
@@ -102,8 +102,17 @@ module Jade
|
|
|
102
102
|
end
|
|
103
103
|
end
|
|
104
104
|
|
|
105
|
+
column_errors = Extensions.check_call(
|
|
106
|
+
callee_name(callee),
|
|
107
|
+
args_acc.types.map { st.env.substitution.apply(it) },
|
|
108
|
+
node,
|
|
109
|
+
registry,
|
|
110
|
+
st.env.entry_name,
|
|
111
|
+
node.range,
|
|
112
|
+
)
|
|
113
|
+
|
|
105
114
|
st
|
|
106
|
-
.add_errors(callee_errors + args_errors)
|
|
115
|
+
.add_errors(callee_errors + args_errors + column_errors)
|
|
107
116
|
.then { [it, base_rs.with(constraints: propagated)] }
|
|
108
117
|
end
|
|
109
118
|
end
|
|
@@ -114,6 +123,17 @@ module Jade
|
|
|
114
123
|
# still holds free vars (`Decodable(List(a))`) surfaces its deps as
|
|
115
124
|
# markers of their own — unindexed, since they occupy no slot in
|
|
116
125
|
# this call's dictionary list.
|
|
126
|
+
# A call through a local binding or a lambda has no name to give a
|
|
127
|
+
# check, which asks by qualified name.
|
|
128
|
+
def callee_name(callee)
|
|
129
|
+
case callee
|
|
130
|
+
in AST::VariableReference(symbol: Symbol::Variable) then nil
|
|
131
|
+
in AST::VariableReference(symbol:) then symbol.qualified_name
|
|
132
|
+
in AST::QualifiedAccess(symbol:) then symbol.qualified_name
|
|
133
|
+
else nil
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
117
137
|
def propagate(constraint, registry, entry_name)
|
|
118
138
|
return [constraint] if constraint.type.is_a?(Type::Var)
|
|
119
139
|
return [] if constraint.unbound_vars.empty?
|
data/lib/jade/task.rb
CHANGED
|
@@ -2,101 +2,121 @@ require 'jade/tasks'
|
|
|
2
2
|
|
|
3
3
|
module Jade
|
|
4
4
|
module Task
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
in
|
|
5
|
+
# Every node is a value describing work, and `run` drives them from one
|
|
6
|
+
# loop with an explicit stack of continuations. Composing tasks therefore
|
|
7
|
+
# costs heap, not Ruby stack, so a chain built by recursion (a batch loop,
|
|
8
|
+
# a retry) is bounded by memory rather than by SystemStackError.
|
|
9
|
+
def run
|
|
10
|
+
task = self
|
|
11
|
+
pending = []
|
|
12
|
+
|
|
13
|
+
loop do
|
|
14
|
+
case task
|
|
15
|
+
in AndThen[inner, fn]
|
|
16
|
+
task = inner
|
|
17
|
+
pending << [:ok, fn]
|
|
18
|
+
|
|
19
|
+
in OnError[inner, fn]
|
|
20
|
+
task = inner
|
|
21
|
+
pending << [:err, fn]
|
|
22
|
+
|
|
23
|
+
in Map[inner, fn]
|
|
24
|
+
task = inner
|
|
25
|
+
pending << [:map, fn]
|
|
26
|
+
|
|
27
|
+
in MapError[inner, fn]
|
|
28
|
+
task = inner
|
|
29
|
+
pending << [:map_err, fn]
|
|
30
|
+
|
|
31
|
+
in Decoded[inner, ok_decoder, err_decoder]
|
|
32
|
+
task = inner
|
|
33
|
+
pending << [:decode, [ok_decoder, err_decoder]]
|
|
34
|
+
|
|
35
|
+
else
|
|
36
|
+
result = task.step
|
|
37
|
+
return result if pending.empty?
|
|
38
|
+
|
|
39
|
+
task = resume(result, pending)
|
|
40
|
+
return task if task.is_a?(Jade::Result::Ok) || task.is_a?(Jade::Result::Err)
|
|
28
41
|
end
|
|
29
42
|
end
|
|
30
43
|
end
|
|
31
44
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# Applies continuations to a settled result until one of them produces a
|
|
48
|
+
# fresh task to run, or the stack empties.
|
|
49
|
+
def resume(result, pending)
|
|
50
|
+
until pending.empty?
|
|
51
|
+
kind, fn = pending.pop
|
|
52
|
+
|
|
53
|
+
result =
|
|
54
|
+
case [kind, result]
|
|
55
|
+
in [:decode, _] then Decoded.decode_result(result, *fn)
|
|
56
|
+
in [:ok, Jade::Result::Ok[value]] then return fn.call(value)
|
|
57
|
+
in [:err, Jade::Result::Err[error]] then return fn.call(error)
|
|
58
|
+
in [:map, Jade::Result::Ok[value]] then Jade::Result::Ok[fn.call(value)]
|
|
59
|
+
in [:map_err, Jade::Result::Err[error]] then Jade::Result::Err[fn.call(error)]
|
|
60
|
+
else result
|
|
61
|
+
end
|
|
40
62
|
end
|
|
63
|
+
|
|
64
|
+
result
|
|
41
65
|
end
|
|
42
66
|
|
|
43
|
-
|
|
67
|
+
Literal = Data.define(:result) do
|
|
44
68
|
include Task
|
|
45
69
|
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
-
in Jade::Result::Ok => ok then ok
|
|
49
|
-
in Jade::Result::Err[error] then fn.call(error).run
|
|
50
|
-
end
|
|
70
|
+
def step
|
|
71
|
+
result
|
|
51
72
|
end
|
|
52
73
|
end
|
|
53
74
|
|
|
54
|
-
|
|
75
|
+
Dispatch = Data.define(:task_def, :args) do
|
|
55
76
|
include Task
|
|
56
77
|
|
|
57
|
-
def
|
|
58
|
-
|
|
59
|
-
in Jade::Result::Ok => ok then ok
|
|
60
|
-
in Jade::Result::Err[error] then Jade::Result::Err[fn.call(error)]
|
|
61
|
-
end
|
|
78
|
+
def step
|
|
79
|
+
Jade::Tasks.dispatch(task_def, *args)
|
|
62
80
|
end
|
|
63
81
|
end
|
|
64
82
|
|
|
65
83
|
Sequence = Data.define(:tasks) do
|
|
66
84
|
include Task
|
|
67
85
|
|
|
68
|
-
def
|
|
86
|
+
def step
|
|
69
87
|
values = []
|
|
88
|
+
|
|
70
89
|
tasks.each do |task|
|
|
71
90
|
case task.run
|
|
72
91
|
in Jade::Result::Ok[value] then values << value
|
|
73
92
|
in Jade::Result::Err => err then return err
|
|
74
93
|
end
|
|
75
94
|
end
|
|
95
|
+
|
|
76
96
|
Jade::Result::Ok[values]
|
|
77
97
|
end
|
|
78
98
|
end
|
|
79
99
|
|
|
100
|
+
Map = Data.define(:task, :fn) { include Task }
|
|
101
|
+
AndThen = Data.define(:task, :fn) { include Task }
|
|
102
|
+
OnError = Data.define(:task, :fn) { include Task }
|
|
103
|
+
MapError = Data.define(:task, :fn) { include Task }
|
|
104
|
+
|
|
80
105
|
Decoded = Data.define(:task, :ok_decoder, :err_decoder) do
|
|
81
106
|
include Task
|
|
82
107
|
|
|
83
|
-
def
|
|
84
|
-
case
|
|
85
|
-
in Jade::Result::Ok[value]
|
|
86
|
-
|
|
87
|
-
in Jade::Result::Err[error]
|
|
88
|
-
Jade::Result::Err[decode(err_decoder, error)]
|
|
108
|
+
def self.decode_result(result, ok_decoder, err_decoder)
|
|
109
|
+
case result
|
|
110
|
+
in Jade::Result::Ok[value] then Jade::Result::Ok[decode(ok_decoder, value)]
|
|
111
|
+
in Jade::Result::Err[error] then Jade::Result::Err[decode(err_decoder, error)]
|
|
89
112
|
end
|
|
90
113
|
end
|
|
91
114
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def decode(decoder, value)
|
|
115
|
+
def self.decode(decoder, value)
|
|
95
116
|
Jade::Decode::Runner.run!(decoder, value) do |error|
|
|
96
117
|
fail Jade::Interop::DecodeError.new(error, value, source: :port_return)
|
|
97
118
|
end
|
|
98
119
|
end
|
|
99
120
|
end
|
|
100
|
-
|
|
101
121
|
end
|
|
102
122
|
end
|
data/lib/jade/version.rb
CHANGED
data/lib/jade.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.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Agustin Cornu
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-08-
|
|
10
|
+
date: 2026-08-21 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: base64
|
|
@@ -93,6 +93,7 @@ files:
|
|
|
93
93
|
- lib/jade/did_you_mean.rb
|
|
94
94
|
- lib/jade/entry.rb
|
|
95
95
|
- lib/jade/error.rb
|
|
96
|
+
- lib/jade/extensions.rb
|
|
96
97
|
- lib/jade/formatter.rb
|
|
97
98
|
- lib/jade/formatter/accesses.rb
|
|
98
99
|
- lib/jade/formatter/bindings.rb
|
|
@@ -157,6 +158,7 @@ files:
|
|
|
157
158
|
- lib/jade/frontend/semantic_analysis/error/constructor_pattern_arity_mismatch.rb
|
|
158
159
|
- lib/jade/frontend/semantic_analysis/error/duplicate_field.rb
|
|
159
160
|
- lib/jade/frontend/semantic_analysis/error/duplicate_function_declaration.rb
|
|
161
|
+
- lib/jade/frontend/semantic_analysis/error/duplicate_implementation.rb
|
|
160
162
|
- lib/jade/frontend/semantic_analysis/error/duplicate_record_field.rb
|
|
161
163
|
- lib/jade/frontend/semantic_analysis/error/invalid_list_rest_pattern.rb
|
|
162
164
|
- lib/jade/frontend/semantic_analysis/error/kwargs_on_non_constructor.rb
|
|
@@ -223,7 +225,6 @@ files:
|
|
|
223
225
|
- lib/jade/frontend/type_checking/constraints/deriving/eq.rb
|
|
224
226
|
- lib/jade/frontend/type_checking/constraints/deriving/helpers.rb
|
|
225
227
|
- lib/jade/frontend/type_checking/constraints/deriving/show.rb
|
|
226
|
-
- lib/jade/frontend/type_checking/constraints/deriving/sql_mapper.rb
|
|
227
228
|
- lib/jade/frontend/type_checking/definition.rb
|
|
228
229
|
- lib/jade/frontend/type_checking/env.rb
|
|
229
230
|
- lib/jade/frontend/type_checking/error.rb
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
module Jade
|
|
2
|
-
module Frontend
|
|
3
|
-
module TypeChecking
|
|
4
|
-
module Constraints
|
|
5
|
-
module Deriving
|
|
6
|
-
# jade-sql's interface, derived here rather than there: the
|
|
7
|
-
# deriving framework is not extensible, and this stays inert
|
|
8
|
-
# without jade-sql, since nothing else names the interface.
|
|
9
|
-
module SqlMapper
|
|
10
|
-
extend self
|
|
11
|
-
include Helpers
|
|
12
|
-
|
|
13
|
-
INTERFACE = 'Sql.SqlMapper'
|
|
14
|
-
ASSIGNMENT = 'Sql.Assignment'
|
|
15
|
-
ASSIGNMENT_FIELDS = %w[col value_sql params].freeze
|
|
16
|
-
|
|
17
|
-
def supports?(interface) = interface == INTERFACE
|
|
18
|
-
|
|
19
|
-
def derive(constraint, registry, entry_name, &lookup)
|
|
20
|
-
return failed(constraint, entry_name) unless assignment_matches?(registry)
|
|
21
|
-
|
|
22
|
-
case constraint.type
|
|
23
|
-
in Type::Application(constructor: Type::Constructor(name:), args: [])
|
|
24
|
-
Symbol
|
|
25
|
-
.type_ref_from_qualified_name(name)
|
|
26
|
-
.then { registry.lookup(it) }
|
|
27
|
-
.then { derive_for(constraint, it, registry, lookup, entry_name) }
|
|
28
|
-
|
|
29
|
-
else
|
|
30
|
-
failed(constraint, entry_name)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
private
|
|
35
|
-
|
|
36
|
-
# The interface is matched by name, so some other module called
|
|
37
|
-
# `Sql` would otherwise be derived against as though it were
|
|
38
|
-
# jade-sql — building its Assignment with the wrong arity, and
|
|
39
|
-
# failing at runtime rather than here.
|
|
40
|
-
def assignment_matches?(registry)
|
|
41
|
-
Symbol
|
|
42
|
-
.type_ref_from_qualified_name(ASSIGNMENT)
|
|
43
|
-
.then { registry.lookup(it) }
|
|
44
|
-
.then do
|
|
45
|
-
it in Symbol::Struct(record_type: { fields: }) and
|
|
46
|
-
fields.keys.map(&:to_s) == ASSIGNMENT_FIELDS
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def derive_for(constraint, symbol, registry, lookup, entry_name)
|
|
51
|
-
case symbol
|
|
52
|
-
in Symbol::Union if single_payload?(symbol, registry)
|
|
53
|
-
derive_union(constraint, symbol, registry, lookup, entry_name)
|
|
54
|
-
|
|
55
|
-
else
|
|
56
|
-
failed(constraint, entry_name)
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# One column per variant, so each variant carries exactly the
|
|
61
|
-
# value that column is set to.
|
|
62
|
-
def single_payload?(union_sym, registry)
|
|
63
|
-
variants(union_sym, registry)
|
|
64
|
-
.then { it.any? && it.all? { it.args.size == 1 } }
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def derive_union(constraint, union_sym, registry, lookup, entry_name)
|
|
68
|
-
vs = variants(union_sym, registry)
|
|
69
|
-
|
|
70
|
-
vs
|
|
71
|
-
.map { encodable_dep(it, registry) }
|
|
72
|
-
.map { lookup.call(it) }
|
|
73
|
-
.then { Results.sequence(it) }
|
|
74
|
-
.map { implementation(constraint, union_body(vs), it) }
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def encodable_dep(variant, registry)
|
|
78
|
-
variant
|
|
79
|
-
.args
|
|
80
|
-
.first
|
|
81
|
-
.then { instantiate(it, {}, registry) }
|
|
82
|
-
.then { Type.constraint('Encode.Encodable', it, nil) }
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def union_body(variants)
|
|
86
|
-
variants
|
|
87
|
-
.each_with_index
|
|
88
|
-
.map { |v, idx| [[:constructor, v.qualified_name, ['x']], [assignment(v, idx)]] }
|
|
89
|
-
.then { [:case, [:var, 'f'], it] }
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def assignment(variant, idx)
|
|
93
|
-
[:call,
|
|
94
|
-
[:struct_constructor, ASSIGNMENT, 3],
|
|
95
|
-
[
|
|
96
|
-
wire_name(variant),
|
|
97
|
-
'?',
|
|
98
|
-
[:list, [[:call, [:impl_arg, idx, 'encoder'], [[:var, 'x']]]]],
|
|
99
|
-
],
|
|
100
|
-
]
|
|
101
|
-
.then { [:list, [it]] }
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def failed(constraint, entry_name)
|
|
105
|
-
Err[
|
|
106
|
-
Error::DerivationFailed.new(
|
|
107
|
-
entry_name, constraint.origin&.range, constraint:, trace: [],
|
|
108
|
-
)
|
|
109
|
-
]
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def implementation(constraint, body, deps)
|
|
113
|
-
Symbol::Implementation.new(
|
|
114
|
-
module_name: nil,
|
|
115
|
-
interface: Symbol.type_ref_from_qualified_name(constraint.interface),
|
|
116
|
-
type: constraint.type,
|
|
117
|
-
type_params: [],
|
|
118
|
-
constraints: [],
|
|
119
|
-
functions: { 'to_assigns' => Symbol::DerivedFunction.new(params: ['f'], body:) },
|
|
120
|
-
deps:,
|
|
121
|
-
extends: [],
|
|
122
|
-
decl_span: nil,
|
|
123
|
-
)
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
end
|