jade-lang 0.8.0 → 0.10.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 +439 -2
- data/docs/interop.md +18 -1
- data/docs/stdlib.md +3 -1
- data/docs/syntax.md +96 -0
- data/lib/jade/api.rb +9 -0
- data/lib/jade/ast.rb +37 -1
- data/lib/jade/cli/eject.rb +132 -0
- data/lib/jade/cli.rb +2 -0
- data/lib/jade/codegen/boundary/specialized/list.rb +11 -7
- data/lib/jade/codegen/boundary/specialized/maybe.rb +2 -2
- data/lib/jade/codegen/boundary/specialized/record.rb +19 -4
- data/lib/jade/codegen/boundary/specialized/scalar.rb +2 -2
- data/lib/jade/codegen/boundary/specialized.rb +13 -5
- data/lib/jade/codegen/boundary.rb +12 -2
- data/lib/jade/codegen/context.rb +17 -0
- data/lib/jade/codegen/error.rb +1 -0
- data/lib/jade/codegen/function_call.rb +23 -7
- data/lib/jade/codegen/function_declaration.rb +25 -13
- data/lib/jade/codegen/helpers.rb +27 -4
- data/lib/jade/codegen/implementation.rb +2 -2
- data/lib/jade/codegen/inline.rb +1 -1
- data/lib/jade/codegen/inlines.rb +6 -1
- data/lib/jade/codegen/names.rb +31 -0
- data/lib/jade/codegen/pattern/constructor.rb +2 -2
- data/lib/jade/codegen/transforms/fold_shape.rb +1 -1
- data/lib/jade/codegen.rb +76 -15
- data/lib/jade/compiler.rb +2 -0
- data/lib/jade/entry.rb +1 -1
- data/lib/jade/error.rb +5 -0
- data/lib/jade/extensions.rb +70 -0
- data/lib/jade/formatter/calls.rb +68 -11
- data/lib/jade/formatter/collections.rb +4 -4
- data/lib/jade/formatter/declarations.rb +13 -15
- data/lib/jade/formatter/helper.rb +20 -0
- data/lib/jade/formatter/infix_application.rb +19 -4
- data/lib/jade/formatter/lambda.rb +24 -12
- data/lib/jade/formatter/pattern.rb +3 -0
- data/lib/jade/frontend/desugaring/placeholder.rb +40 -17
- data/lib/jade/frontend/desugaring.rb +29 -9
- data/lib/jade/frontend/fixity_fixer.rb +6 -4
- data/lib/jade/frontend/forward_declaration/error/alias_expansion.rb +23 -0
- data/lib/jade/frontend/forward_declaration/error/duplicate_constructor_name.rb +45 -0
- data/lib/jade/frontend/forward_declaration/error/duplicate_type_name.rb +42 -0
- data/lib/jade/frontend/forward_declaration/error.rb +3 -0
- data/lib/jade/frontend/forward_declaration/helper.rb +58 -0
- data/lib/jade/frontend/forward_declaration/import_declaration.rb +4 -0
- data/lib/jade/frontend/forward_declaration/interface_declaration.rb +6 -0
- data/lib/jade/frontend/forward_declaration/module.rb +4 -0
- data/lib/jade/frontend/forward_declaration/struct_declaration.rb +10 -1
- data/lib/jade/frontend/forward_declaration/type_alias_declaration.rb +37 -0
- data/lib/jade/frontend/forward_declaration/type_declaration.rb +10 -1
- data/lib/jade/frontend/forward_declaration.rb +2 -0
- data/lib/jade/frontend/pattern_analysis/exhaustiveness.rb +47 -43
- data/lib/jade/frontend/pattern_analysis/matrix.rb +28 -219
- data/lib/jade/frontend/pattern_analysis/pattern.rb +132 -0
- data/lib/jade/frontend/pattern_analysis/redundancy.rb +24 -0
- data/lib/jade/frontend/pattern_analysis/signature.rb +220 -0
- data/lib/jade/frontend/pattern_analysis/usefulness.rb +72 -0
- data/lib/jade/frontend/pattern_analysis/witnesses.rb +73 -0
- data/lib/jade/frontend/pattern_analysis.rb +5 -38
- 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/implementation_on_alias.rb +24 -0
- data/lib/jade/frontend/semantic_analysis/error/no_base_case.rb +45 -0
- data/lib/jade/frontend/semantic_analysis/error/placeholder_not_allowed.rb +15 -3
- data/lib/jade/frontend/semantic_analysis/error/recursive_type_alias.rb +24 -0
- data/lib/jade/frontend/semantic_analysis/error.rb +4 -0
- data/lib/jade/frontend/semantic_analysis/helper.rb +41 -0
- data/lib/jade/frontend/semantic_analysis/implementation.rb +13 -10
- data/lib/jade/frontend/semantic_analysis/inhabitedness.rb +90 -0
- data/lib/jade/frontend/semantic_analysis/interop_import_declaration.rb +28 -16
- data/lib/jade/frontend/semantic_analysis/keyed_call.rb +21 -5
- data/lib/jade/frontend/semantic_analysis/pattern_range.rb +16 -0
- data/lib/jade/frontend/semantic_analysis/struct_declaration.rb +1 -0
- data/lib/jade/frontend/semantic_analysis/type_alias_declaration/cycle_detection.rb +95 -0
- data/lib/jade/frontend/semantic_analysis/type_alias_declaration.rb +48 -0
- data/lib/jade/frontend/semantic_analysis/type_declaration.rb +1 -0
- data/lib/jade/frontend/semantic_analysis.rb +5 -0
- data/lib/jade/frontend/type_checking/canonicalize.rb +1 -0
- data/lib/jade/frontend/type_checking/cascade.rb +90 -0
- data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +10 -0
- data/lib/jade/frontend/type_checking/constraints/deriving.rb +5 -4
- data/lib/jade/frontend/type_checking/constraints.rb +18 -3
- data/lib/jade/frontend/type_checking/error/display.rb +49 -0
- data/lib/jade/frontend/type_checking/error/division_by_zero.rb +31 -0
- data/lib/jade/frontend/type_checking/error/empty_range_pattern.rb +25 -0
- data/lib/jade/frontend/type_checking/error/function_body_type_mismatch.rb +4 -3
- data/lib/jade/frontend/type_checking/error/function_call_type_mismatch.rb +108 -5
- data/lib/jade/frontend/type_checking/error/if_branch_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/if_branches_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/if_condition_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/implementation_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/list_item_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/missing_patterns.rb +20 -4
- data/lib/jade/frontend/type_checking/error/pattern_type_mismatch.rb +7 -1
- data/lib/jade/frontend/type_checking/error/range_pattern_type.rb +29 -0
- data/lib/jade/frontend/type_checking/error/record_access_type_mismatch.rb +2 -2
- data/lib/jade/frontend/type_checking/error/recursive_derivation.rb +25 -0
- data/lib/jade/frontend/type_checking/error/type_mismatch.rb +10 -2
- data/lib/jade/frontend/type_checking/error/unreachable_branch.rb +22 -0
- data/lib/jade/frontend/type_checking/error.rb +6 -0
- data/lib/jade/frontend/type_checking/inference/assign.rb +1 -1
- data/lib/jade/frontend/type_checking/inference/case_of.rb +21 -4
- data/lib/jade/frontend/type_checking/inference/division.rb +50 -0
- data/lib/jade/frontend/type_checking/inference/function_call.rb +47 -2
- data/lib/jade/frontend/type_checking/inference/lambda.rb +1 -1
- data/lib/jade/frontend/type_checking/inference/pattern.rb +35 -0
- data/lib/jade/frontend/type_checking/inference/type_alias_declaration.rb +18 -0
- data/lib/jade/frontend/type_checking/inference.rb +2 -0
- data/lib/jade/frontend/type_checking/loader.rb +11 -0
- data/lib/jade/frontend/type_checking/port_resolution.rb +26 -8
- data/lib/jade/frontend/type_checking/substitution.rb +2 -2
- data/lib/jade/frontend/type_checking.rb +3 -1
- data/lib/jade/frontend/usage_analysis.rb +5 -1
- data/lib/jade/interop/boundary.rb +64 -24
- data/lib/jade/interop/error.rb +35 -7
- data/lib/jade/interop/lowering.rb +8 -2
- data/lib/jade/lsp/converters.rb +83 -2
- data/lib/jade/lsp/handlers.rb +122 -25
- data/lib/jade/lsp/snippets.rb +12 -0
- data/lib/jade/lsp/state.rb +11 -4
- data/lib/jade/module_loader/build.rb +36 -0
- data/lib/jade/module_loader.rb +1 -0
- data/lib/jade/parsing/combinators.rb +78 -2
- data/lib/jade/parsing/error.rb +43 -6
- data/lib/jade/parsing/type.rb +5 -3
- data/lib/jade/parsing.rb +140 -26
- data/lib/jade/runtime.rb +32 -13
- data/lib/jade/source.rb +6 -2
- data/lib/jade/stdlib/basics.rb +22 -3
- data/lib/jade/stdlib/char.rb +2 -0
- data/lib/jade/stdlib/clock.rb +6 -2
- data/lib/jade/stdlib/decimal.rb +51 -14
- data/lib/jade/stdlib/intrinsics.rb +59 -6
- data/lib/jade/stdlib/list.rb +37 -1
- data/lib/jade/stdlib/number.rb +47 -0
- data/lib/jade/stdlib/range.rb +211 -0
- data/lib/jade/stdlib.rb +11 -4
- data/lib/jade/symbol/alias.rb +11 -0
- data/lib/jade/symbol/parser.rb +1 -1
- data/lib/jade/symbol.rb +13 -1
- data/lib/jade/task.rb +74 -56
- data/lib/jade/type/anonymous_record.rb +11 -2
- data/lib/jade/type/application.rb +11 -2
- data/lib/jade/type/base.rb +26 -0
- data/lib/jade/type/function.rb +11 -2
- data/lib/jade/type.rb +36 -10
- data/lib/jade/version.rb +1 -1
- data/lib/jade.rb +1 -0
- metadata +35 -3
- data/lib/jade/frontend/type_checking/constraints/deriving/assignable.rb +0 -170
|
@@ -28,10 +28,12 @@ module Jade
|
|
|
28
28
|
.then { |(new_state, result)| [new_state, acc.add(result)] }
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
args_state = args_state.add_errors(Division.by_zero(node, args_state.env.entry_name))
|
|
32
|
+
|
|
31
33
|
after_callee_state, result_type = unify_callee(
|
|
32
34
|
args_state,
|
|
33
35
|
callee_result,
|
|
34
|
-
args_acc,
|
|
36
|
+
Division.lift(node, args_acc),
|
|
35
37
|
node,
|
|
36
38
|
state,
|
|
37
39
|
)
|
|
@@ -102,8 +104,17 @@ module Jade
|
|
|
102
104
|
end
|
|
103
105
|
end
|
|
104
106
|
|
|
107
|
+
column_errors = Extensions.check_call(
|
|
108
|
+
callee_name(callee),
|
|
109
|
+
args_acc.types.map { st.env.substitution.apply(it) },
|
|
110
|
+
node,
|
|
111
|
+
registry,
|
|
112
|
+
st.env.entry_name,
|
|
113
|
+
node.range,
|
|
114
|
+
)
|
|
115
|
+
|
|
105
116
|
st
|
|
106
|
-
.add_errors(callee_errors + args_errors)
|
|
117
|
+
.add_errors(callee_errors + args_errors + column_errors)
|
|
107
118
|
.then { [it, base_rs.with(constraints: propagated)] }
|
|
108
119
|
end
|
|
109
120
|
end
|
|
@@ -114,6 +125,17 @@ module Jade
|
|
|
114
125
|
# still holds free vars (`Decodable(List(a))`) surfaces its deps as
|
|
115
126
|
# markers of their own — unindexed, since they occupy no slot in
|
|
116
127
|
# this call's dictionary list.
|
|
128
|
+
# A call through a local binding or a lambda has no name to give a
|
|
129
|
+
# check, which asks by qualified name.
|
|
130
|
+
def callee_name(callee)
|
|
131
|
+
case callee
|
|
132
|
+
in AST::VariableReference(symbol: Symbol::Variable) then nil
|
|
133
|
+
in AST::VariableReference(symbol:) then symbol.qualified_name
|
|
134
|
+
in AST::QualifiedAccess(symbol:) then symbol.qualified_name
|
|
135
|
+
else nil
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
117
139
|
def propagate(constraint, registry, entry_name)
|
|
118
140
|
return [constraint] if constraint.type.is_a?(Type::Var)
|
|
119
141
|
return [] if constraint.unbound_vars.empty?
|
|
@@ -150,9 +172,32 @@ module Jade
|
|
|
150
172
|
expected: e.expected,
|
|
151
173
|
actual: e.actual,
|
|
152
174
|
infix: node.infix,
|
|
175
|
+
lifted_placeholder: lifted_placeholder?(node),
|
|
176
|
+
callee_name: source_name(node.callee),
|
|
177
|
+
arg_names: node.args.map { source_name(it) },
|
|
153
178
|
)
|
|
154
179
|
end
|
|
155
180
|
end
|
|
181
|
+
|
|
182
|
+
# Desugaring lifted the `_` into a lambda parameter, so a hole
|
|
183
|
+
# reads as an argument referring to one by the time we get here.
|
|
184
|
+
def lifted_placeholder?(node)
|
|
185
|
+
node.args.any? { it in AST::VariableReference(name: /\A__p\d+__\z/) }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# What the author called this, where they called it something.
|
|
189
|
+
def source_name(node)
|
|
190
|
+
case node
|
|
191
|
+
in AST::ConstructorReference | AST::VariableReference
|
|
192
|
+
node.name
|
|
193
|
+
|
|
194
|
+
in AST::QualifiedAccess(path:)
|
|
195
|
+
path.join('.')
|
|
196
|
+
|
|
197
|
+
else
|
|
198
|
+
nil
|
|
199
|
+
end
|
|
200
|
+
end
|
|
156
201
|
end
|
|
157
202
|
end
|
|
158
203
|
end
|
|
@@ -11,6 +11,32 @@ module Jade
|
|
|
11
11
|
.then { |s, r| [s.with(env: s.env.pin_type(pattern.id, r.type)), r] }
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
def range_type_error(state, pattern)
|
|
15
|
+
->(error) do
|
|
16
|
+
Error::RangePatternType.new(
|
|
17
|
+
state.env.entry_name, pattern.range,
|
|
18
|
+
expected: error.expected, actual: error.actual,
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def empty_range(pattern, state, result)
|
|
24
|
+
pattern => AST::Pattern::Range(from:, to:)
|
|
25
|
+
|
|
26
|
+
return [] unless state.env.substitution.apply(result.type) in Type::Application(
|
|
27
|
+
constructor: Type::Constructor(name: 'Basics.Int'),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
return [] unless from && to && from.value > to.value
|
|
31
|
+
|
|
32
|
+
[Error::EmptyRangePattern.new(
|
|
33
|
+
state.env.entry_name,
|
|
34
|
+
pattern.range,
|
|
35
|
+
from: from.value,
|
|
36
|
+
to: to.value,
|
|
37
|
+
)]
|
|
38
|
+
end
|
|
39
|
+
|
|
14
40
|
def infer_(pattern, registry, state, expected)
|
|
15
41
|
case pattern
|
|
16
42
|
in AST::Pattern::Record(fields:, symbol:)
|
|
@@ -34,6 +60,15 @@ module Jade
|
|
|
34
60
|
.unify_result(it, expected.type, &type_error(state, pattern))
|
|
35
61
|
end
|
|
36
62
|
|
|
63
|
+
in AST::Pattern::Range
|
|
64
|
+
state
|
|
65
|
+
.unify_result(
|
|
66
|
+
Result.init(Type.int),
|
|
67
|
+
expected.type,
|
|
68
|
+
&range_type_error(state, pattern)
|
|
69
|
+
)
|
|
70
|
+
.then { |(st, r)| [st.add_errors(empty_range(pattern, st, r)), r] }
|
|
71
|
+
|
|
37
72
|
in AST::Pattern::Literal(literal:)
|
|
38
73
|
new_state, literal_result = check(literal, registry, state, expected)
|
|
39
74
|
new_state.unify_result(literal_result, expected.type, &type_error(state, pattern))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module TypeChecking
|
|
4
|
+
module Inference
|
|
5
|
+
module TypeAliasDeclaration
|
|
6
|
+
extend Helpers
|
|
7
|
+
extend self
|
|
8
|
+
|
|
9
|
+
def infer(node, _, state, _)
|
|
10
|
+
node => AST::TypeAliasDeclaration
|
|
11
|
+
|
|
12
|
+
[state, Result.init(Type.unit)]
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -3,6 +3,7 @@ require 'jade/frontend/type_checking/inference/helpers'
|
|
|
3
3
|
require 'jade/frontend/type_checking/inference/body'
|
|
4
4
|
require 'jade/frontend/type_checking/inference/case_of'
|
|
5
5
|
require 'jade/frontend/type_checking/inference/constructor_reference'
|
|
6
|
+
require 'jade/frontend/type_checking/inference/division'
|
|
6
7
|
require 'jade/frontend/type_checking/inference/function_call'
|
|
7
8
|
require 'jade/frontend/type_checking/inference/function_declaration'
|
|
8
9
|
require 'jade/frontend/type_checking/inference/implementation'
|
|
@@ -21,6 +22,7 @@ require 'jade/frontend/type_checking/inference/record_field'
|
|
|
21
22
|
require 'jade/frontend/type_checking/inference/record_literal'
|
|
22
23
|
require 'jade/frontend/type_checking/inference/record_update'
|
|
23
24
|
require 'jade/frontend/type_checking/inference/struct_declaration'
|
|
25
|
+
require 'jade/frontend/type_checking/inference/type_alias_declaration'
|
|
24
26
|
require 'jade/frontend/type_checking/inference/interface_declaration'
|
|
25
27
|
require 'jade/frontend/type_checking/inference/type_declaration'
|
|
26
28
|
require 'jade/frontend/type_checking/inference/assign'
|
|
@@ -35,12 +35,22 @@ module Jade
|
|
|
35
35
|
entry
|
|
36
36
|
.types
|
|
37
37
|
.reduce(env) do |e, (_, sym)|
|
|
38
|
+
# Aliases are transparent — they get expanded into their
|
|
39
|
+
# body whenever a TypeRef resolves to one. They never need
|
|
40
|
+
# to be looked up as definitions during unification.
|
|
41
|
+
next e if alias_symbol?(sym, registry)
|
|
42
|
+
|
|
38
43
|
Definition
|
|
39
44
|
.from_symbol(sym, registry)
|
|
40
45
|
.then { e.define(sym.qualified_name, it) }
|
|
41
46
|
end
|
|
42
47
|
end
|
|
43
48
|
|
|
49
|
+
def alias_symbol?(sym, registry)
|
|
50
|
+
sym.is_a?(Symbol::Alias) ||
|
|
51
|
+
(sym.is_a?(Symbol::TypeRef) && registry.lookup(sym).is_a?(Symbol::Alias))
|
|
52
|
+
end
|
|
53
|
+
|
|
44
54
|
def load_imports(env, entry, registry)
|
|
45
55
|
entry
|
|
46
56
|
.imports
|
|
@@ -65,6 +75,7 @@ module Jade
|
|
|
65
75
|
|
|
66
76
|
in Symbol::TypeRef
|
|
67
77
|
return env if env.lookup_def(sym.qualified_name)
|
|
78
|
+
return env if registry.lookup(sym).is_a?(Symbol::Alias)
|
|
68
79
|
|
|
69
80
|
Definition
|
|
70
81
|
.from_symbol(sym, registry)
|
|
@@ -36,9 +36,12 @@ module Jade
|
|
|
36
36
|
|
|
37
37
|
private
|
|
38
38
|
|
|
39
|
+
# Every port reaching here returns a Task: `NonTaskPort` runs in
|
|
40
|
+
# semantic analysis and stops compilation before this, which is what
|
|
41
|
+
# lets `resolve_port` destructure the return type without checking.
|
|
39
42
|
def resolve_value(sym, entry, registry)
|
|
40
43
|
case sym
|
|
41
|
-
in Symbol::InteropFunction
|
|
44
|
+
in Symbol::InteropFunction
|
|
42
45
|
resolve_port(sym, entry, registry)
|
|
43
46
|
|
|
44
47
|
else
|
|
@@ -47,7 +50,7 @@ module Jade
|
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
def resolve_port(interop_fn, entry, registry)
|
|
50
|
-
|
|
53
|
+
ok_sym, err_sym = arm_symbols(interop_fn.return_type)
|
|
51
54
|
|
|
52
55
|
# Single Type.from_symbol over the whole port so a var appearing in
|
|
53
56
|
# more than one position gets the same Type::Var id. PortCodec relies
|
|
@@ -66,6 +69,19 @@ module Jade
|
|
|
66
69
|
]
|
|
67
70
|
end
|
|
68
71
|
|
|
72
|
+
# The arms are only wanted for their spans, and an alias keeps its
|
|
73
|
+
# arms in another declaration, so there is often none to point at and
|
|
74
|
+
# the port's own return type is the best there is.
|
|
75
|
+
def arm_symbols(return_type)
|
|
76
|
+
case return_type
|
|
77
|
+
in Symbol::TypeApplication(constructor: Symbol::TypeRef['Task', 'Task'], args: [ok, err])
|
|
78
|
+
[ok, err]
|
|
79
|
+
|
|
80
|
+
else
|
|
81
|
+
[nil, nil]
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
69
85
|
# Without the constraints the shared VarGen would still hand every
|
|
70
86
|
# position its own var; `from_symbol` only ties them together through
|
|
71
87
|
# the map it threads across params and return.
|
|
@@ -83,7 +99,7 @@ module Jade
|
|
|
83
99
|
end
|
|
84
100
|
|
|
85
101
|
def resolve_param(type_sym, type, interop_fn, index, entry, registry)
|
|
86
|
-
return [Symbol::InteropFunction::PASS, []] if pass_through?(
|
|
102
|
+
return [Symbol::InteropFunction::PASS, []] if pass_through?(type)
|
|
87
103
|
|
|
88
104
|
case type
|
|
89
105
|
in Type::Var(name:)
|
|
@@ -99,7 +115,7 @@ module Jade
|
|
|
99
115
|
end
|
|
100
116
|
|
|
101
117
|
def resolve_arm(type_sym, type, interop_fn, arm, entry, registry)
|
|
102
|
-
return [Symbol::InteropFunction::PASS, []] if pass_through?(
|
|
118
|
+
return [Symbol::InteropFunction::PASS, []] if pass_through?(type)
|
|
103
119
|
|
|
104
120
|
case type
|
|
105
121
|
in Type::Var(name:)
|
|
@@ -156,10 +172,12 @@ module Jade
|
|
|
156
172
|
end
|
|
157
173
|
end
|
|
158
174
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
175
|
+
# Asked of the expanded type, so an alias over `Never` or
|
|
176
|
+
# `Decode.Value` passes through the same as the type it names.
|
|
177
|
+
def pass_through?(type)
|
|
178
|
+
case type
|
|
179
|
+
in Type::Application(constructor: Type::Constructor(name: 'Basics.Never' | 'Decode.Value'))
|
|
180
|
+
true
|
|
163
181
|
|
|
164
182
|
else
|
|
165
183
|
false
|
|
@@ -47,7 +47,7 @@ module Jade
|
|
|
47
47
|
in Type::Application(args:)
|
|
48
48
|
case apply(type.constructor)
|
|
49
49
|
in Type::PartialApplication(constructor:, args: tail_args)
|
|
50
|
-
Type::Application[constructor, args.map { apply(it) } + tail_args]
|
|
50
|
+
Type::Application[constructor, args.map { apply(it) } + tail_args, type.display]
|
|
51
51
|
|
|
52
52
|
in constructor
|
|
53
53
|
type.with(constructor:, args: args.map { apply(it) })
|
|
@@ -60,7 +60,7 @@ module Jade
|
|
|
60
60
|
|
|
61
61
|
case apply(row_var)
|
|
62
62
|
in Type::AnonymousRecord(fields: extra_fields, row_var: new_row_var)
|
|
63
|
-
Type.anonymous_record(applied_fields.merge(extra_fields), new_row_var)
|
|
63
|
+
Type.anonymous_record(applied_fields.merge(extra_fields), new_row_var, type.display)
|
|
64
64
|
|
|
65
65
|
in Type::Var => applied_row_var
|
|
66
66
|
type.with(fields: applied_fields, row_var: applied_row_var)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'jade/frontend/type_checking/cascade'
|
|
1
2
|
require 'jade/frontend/type_checking/canonicalize'
|
|
2
3
|
require 'jade/frontend/type_checking/constraints'
|
|
3
4
|
require 'jade/frontend/type_checking/definition'
|
|
@@ -97,7 +98,7 @@ module Jade
|
|
|
97
98
|
# with those constraints — rather than being dropped silently.
|
|
98
99
|
|
|
99
100
|
state
|
|
100
|
-
.with(errors: state.errors + errors)
|
|
101
|
+
.with(errors: Cascade.prune(state.errors + errors))
|
|
101
102
|
.to_result
|
|
102
103
|
end
|
|
103
104
|
|
|
@@ -143,6 +144,7 @@ module Jade
|
|
|
143
144
|
in AST::QualifiedAccess then Inference::QualifiedAccess
|
|
144
145
|
in AST::RecordAccess then Inference::RecordAccess
|
|
145
146
|
in AST::StructDeclaration then Inference::StructDeclaration
|
|
147
|
+
in AST::TypeAliasDeclaration then Inference::TypeAliasDeclaration
|
|
146
148
|
in AST::InterfaceDeclaration then Inference::InterfaceDeclaration
|
|
147
149
|
in AST::RecordField then Inference::RecordField
|
|
148
150
|
in AST::RecordLiteral then Inference::RecordLiteral
|
|
@@ -97,7 +97,8 @@ module Jade
|
|
|
97
97
|
in AST::Pattern::Record(fields:)
|
|
98
98
|
fields.flat_map { walk(it.pattern, :as_value, entry, owner) }
|
|
99
99
|
|
|
100
|
-
in AST::Pattern::Literal | AST::Pattern::Binding | AST::Pattern::Wildcard
|
|
100
|
+
in AST::Pattern::Literal | AST::Pattern::Binding | AST::Pattern::Wildcard |
|
|
101
|
+
AST::Pattern::Range
|
|
101
102
|
[]
|
|
102
103
|
|
|
103
104
|
in AST::Grouping(expression:)
|
|
@@ -138,6 +139,9 @@ module Jade
|
|
|
138
139
|
in AST::StructDeclaration(record_type:, symbol:)
|
|
139
140
|
walk_type(record_type, entry, ReferenceIndex.key_for(symbol))
|
|
140
141
|
|
|
142
|
+
in AST::TypeAliasDeclaration(body_type:, symbol:)
|
|
143
|
+
walk_type(body_type, entry, ReferenceIndex.key_for(symbol))
|
|
144
|
+
|
|
141
145
|
in AST::InterfaceDeclaration(functions:, symbol:)
|
|
142
146
|
ReferenceIndex.key_for(symbol).then do |declared|
|
|
143
147
|
functions.flat_map { walk_type(it.type, entry, declared) }
|
|
@@ -11,6 +11,50 @@ module Jade
|
|
|
11
11
|
# Result wrap/unwrap that user-level Decode.from_value uses is dead
|
|
12
12
|
# weight at the boundary because failure always raises anyway. Skipping
|
|
13
13
|
# it removes one allocation per arg per Ruby→Jade call.
|
|
14
|
+
# Names the argument a failure came from. Costs nothing until one
|
|
15
|
+
# does: Ruby only pays for a rescue that fires.
|
|
16
|
+
def arg(where)
|
|
17
|
+
yield
|
|
18
|
+
rescue DecodeError => e
|
|
19
|
+
raise e.under(where)
|
|
20
|
+
end
|
|
21
|
+
alias at arg
|
|
22
|
+
|
|
23
|
+
# Finding the element costs a second pass, which only a failure pays
|
|
24
|
+
# for: the map stays a map.
|
|
25
|
+
def elements(array, where = nil)
|
|
26
|
+
array.map { yield(it) }
|
|
27
|
+
rescue DecodeError => e
|
|
28
|
+
raise e.under("#{where}[#{failing_index(array) { yield(it) }}]")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def failing_index(array)
|
|
32
|
+
array.each_with_index do |element, index|
|
|
33
|
+
yield(element)
|
|
34
|
+
rescue DecodeError
|
|
35
|
+
return index
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# An absent key reads as nil to the field decoder, which then blames
|
|
40
|
+
# the type it wanted. The path says which key that was, so a struct
|
|
41
|
+
# can check whether it was ever there.
|
|
42
|
+
def blame(error, hash, label)
|
|
43
|
+
key = error.where.to_s[/\A\.([^.\[]+)/, 1]
|
|
44
|
+
return error if key.nil? || hash.key?(key)
|
|
45
|
+
|
|
46
|
+
symbol_keys!(label, hash)
|
|
47
|
+
error.as(Jade::Decode::MissingField[key])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Mixed keys are a real shape mismatch; the per-field errors say more
|
|
51
|
+
# than a guess about intent would.
|
|
52
|
+
def symbol_keys!(label, hash)
|
|
53
|
+
return if hash.empty? || hash.keys.any?(::String)
|
|
54
|
+
|
|
55
|
+
hash.keys.grep(::Symbol).then { raise SymbolKeys.new(label, it) unless it.empty? }
|
|
56
|
+
end
|
|
57
|
+
|
|
14
58
|
def decode_or_raise(decoder, value)
|
|
15
59
|
Jade::Decode::Runner.run!(decoder, value) do |error|
|
|
16
60
|
raise Jade::Interop::DecodeError.new(error, value)
|
|
@@ -21,54 +65,50 @@ module Jade
|
|
|
21
65
|
# argument types in place of the generic `decode_or_raise` path —
|
|
22
66
|
# avoids constructing a Decoder descriptor and walking the
|
|
23
67
|
# interpreter for primitives.
|
|
24
|
-
def integer(label, v)
|
|
25
|
-
::Integer === v ? v : type_error!(label, v)
|
|
68
|
+
def integer(label, v, where = nil)
|
|
69
|
+
::Integer === v ? v : type_error!(label, v, where)
|
|
26
70
|
end
|
|
27
71
|
|
|
28
|
-
def string(label, v)
|
|
29
|
-
::String === v ? v.dup : type_error!(label, v)
|
|
72
|
+
def string(label, v, where = nil)
|
|
73
|
+
::String === v ? v.dup : type_error!(label, v, where)
|
|
30
74
|
end
|
|
31
75
|
|
|
32
|
-
def bool(label, v)
|
|
33
|
-
v == true || v == false ? v : type_error!(label, v)
|
|
76
|
+
def bool(label, v, where = nil)
|
|
77
|
+
v == true || v == false ? v : type_error!(label, v, where)
|
|
34
78
|
end
|
|
35
79
|
|
|
36
|
-
def float(label, v)
|
|
37
|
-
::Numeric === v ? v.to_f : type_error!(label, v)
|
|
80
|
+
def float(label, v, where = nil)
|
|
81
|
+
::Numeric === v ? v.to_f : type_error!(label, v, where)
|
|
38
82
|
end
|
|
39
83
|
|
|
40
|
-
|
|
41
|
-
|
|
84
|
+
# Copied, because the caller keeps its array and Jade values do not
|
|
85
|
+
# change. The copy is under 1% of the check that precedes it.
|
|
86
|
+
def list_of(klass, label, v, where = nil)
|
|
87
|
+
v.is_a?(::Array) && v.all? { klass === _1 } ? v.dup : type_error!(label, v, where)
|
|
42
88
|
end
|
|
43
89
|
|
|
44
90
|
# Validates that v is an Array but doesn't check element types — used
|
|
45
91
|
# when the per-element decoder isn't a simple `is_a?` (e.g. nested
|
|
46
92
|
# structs). The caller maps a decoder over the result.
|
|
47
|
-
def array(label, v)
|
|
48
|
-
v.is_a?(::Array) ? v : type_error!(label, v)
|
|
93
|
+
def array(label, v, where = nil)
|
|
94
|
+
v.is_a?(::Array) ? v : type_error!(label, v, where)
|
|
49
95
|
end
|
|
50
96
|
|
|
97
|
+
# Symbol keys are not checked here: every field would then be
|
|
98
|
+
# missing, and the rescue around the fields catches that for free.
|
|
51
99
|
def hash(label, v)
|
|
52
100
|
case v
|
|
53
|
-
when ::Hash then
|
|
101
|
+
when ::Hash then v
|
|
54
102
|
when ::Data then v.to_h.transform_keys(&:to_s)
|
|
55
|
-
else type_error!(label, v)
|
|
103
|
+
else type_error!(label, v, nil)
|
|
56
104
|
end
|
|
57
105
|
end
|
|
58
106
|
|
|
59
|
-
|
|
60
|
-
# than a guess about intent would.
|
|
61
|
-
def string_keyed!(label, v)
|
|
62
|
-
return v if v.empty? || v.keys.any?(::String)
|
|
63
|
-
|
|
64
|
-
v.keys.grep(::Symbol)
|
|
65
|
-
.then { it.empty? ? v : raise(Jade::Interop::SymbolKeys.new(label, it)) }
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def type_error!(label, v)
|
|
107
|
+
def type_error!(label, v, where)
|
|
69
108
|
raise Jade::Interop::DecodeError.new(
|
|
70
109
|
Jade::Decode::WrongType[label, Jade::Decode.type_name(v)],
|
|
71
110
|
v,
|
|
111
|
+
where:,
|
|
72
112
|
)
|
|
73
113
|
end
|
|
74
114
|
end
|
data/lib/jade/interop/error.rb
CHANGED
|
@@ -54,36 +54,48 @@ module Jade
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
class DecodeError < Error
|
|
57
|
-
attr_reader :decode_error, :value, :source
|
|
57
|
+
attr_reader :decode_error, :value, :source, :where
|
|
58
58
|
|
|
59
59
|
SUBJECTS = {
|
|
60
60
|
argument: 'Ruby passed a value that failed to decode',
|
|
61
61
|
port_return: 'Port returned a value that failed to decode',
|
|
62
62
|
}.freeze
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
# Ruby has no null.
|
|
65
|
+
TYPE_NAMES = { 'null' => 'nil' }.freeze
|
|
66
|
+
|
|
67
|
+
def initialize(decode_error, value, source: :argument, where: nil)
|
|
65
68
|
@decode_error = decode_error
|
|
66
69
|
@value = value
|
|
67
70
|
@source = source
|
|
71
|
+
@where = where
|
|
68
72
|
super(format(decode_error, value))
|
|
69
73
|
end
|
|
70
74
|
|
|
75
|
+
# Frames unwind innermost first, so each one puts itself in front of
|
|
76
|
+
# the path the frames below it built.
|
|
77
|
+
def under(segment)
|
|
78
|
+
self.class.new(decode_error, value, source:, where: "#{segment}#{where}")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def as(error)
|
|
82
|
+
self.class.new(error, value, source:, where:)
|
|
83
|
+
end
|
|
84
|
+
|
|
71
85
|
private
|
|
72
86
|
|
|
73
87
|
def format(error, value)
|
|
74
88
|
path, leaf = unwind(error)
|
|
75
|
-
location = path.empty? ? "value" : path.join
|
|
76
|
-
subject = SUBJECTS.fetch(@source)
|
|
77
89
|
|
|
78
90
|
case leaf
|
|
79
91
|
in Jade::Decode::WrongType[expected, got]
|
|
80
|
-
"#{subject}
|
|
92
|
+
"#{subject(path)}: expected #{expected}, got #{type_name(got)}#{seen(value)}"
|
|
81
93
|
|
|
82
94
|
in Jade::Decode::MissingField[key]
|
|
83
|
-
"#{subject}
|
|
95
|
+
"#{subject(path)}: missing field `#{key}`#{seen(value)}"
|
|
84
96
|
|
|
85
97
|
in Jade::Decode::Custom[msg]
|
|
86
|
-
"#{subject}
|
|
98
|
+
"#{subject(path)}: #{msg}#{seen(value)}"
|
|
87
99
|
|
|
88
100
|
in Jade::Decode::Multiple[errors]
|
|
89
101
|
errors
|
|
@@ -92,6 +104,22 @@ module Jade
|
|
|
92
104
|
end
|
|
93
105
|
end
|
|
94
106
|
|
|
107
|
+
# `Shop.price(item).cents` when the caller said where it was, and the
|
|
108
|
+
# old sentence when it did not.
|
|
109
|
+
def subject(path)
|
|
110
|
+
return "#{SUBJECTS.fetch(@source)} at #{path.empty? ? 'value' : path.join}" unless @where
|
|
111
|
+
|
|
112
|
+
"#{@where}#{path.join}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def type_name(got)
|
|
116
|
+
TYPE_NAMES.fetch(got.to_s, got)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def seen(value)
|
|
120
|
+
value.nil? ? '' : " (#{value.inspect})"
|
|
121
|
+
end
|
|
122
|
+
|
|
95
123
|
def unwind(error, path = [])
|
|
96
124
|
case error
|
|
97
125
|
in Jade::Decode::AtField[key, inner] then unwind(inner, path + [".#{key}"])
|
|
@@ -21,8 +21,9 @@ module Jade
|
|
|
21
21
|
in Symbol::FunctionType
|
|
22
22
|
[FunctionError.new('inline function type')]
|
|
23
23
|
|
|
24
|
-
in Symbol::TypeApplication(args:)
|
|
25
|
-
|
|
24
|
+
in Symbol::TypeApplication(constructor:, args:)
|
|
25
|
+
validate(constructor, registry, entry) +
|
|
26
|
+
args.flat_map { validate(it, registry, entry) }
|
|
26
27
|
|
|
27
28
|
in Symbol::RecordType(fields:)
|
|
28
29
|
fields.values.flat_map { validate(it, registry, entry) }
|
|
@@ -34,6 +35,11 @@ module Jade
|
|
|
34
35
|
lookup_type(symbol, registry, entry)
|
|
35
36
|
&.then { validate(it, registry, entry) } || []
|
|
36
37
|
|
|
38
|
+
# An alias is its body here as everywhere else, so a function type
|
|
39
|
+
# behind one is still a function type that cannot be lowered.
|
|
40
|
+
in Symbol::Alias(body:)
|
|
41
|
+
body ? validate(body, registry, entry) : []
|
|
42
|
+
|
|
37
43
|
else
|
|
38
44
|
[]
|
|
39
45
|
end
|