jade-lang 0.12.0 → 0.12.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e6f1ff92a99cc704dd81615de9b97ffd0f7e650ca23adac6c7ede429eeb8ffe
4
- data.tar.gz: ba35d16db8a3ff88f46ef726df1f19c27fc93a20366d0acd1faec55764690f9c
3
+ metadata.gz: 1b6d2de0940d8842121c48456180fa3cbc04345d0bf991c31f8e008019c4e017
4
+ data.tar.gz: 425323f1b3c92b1d817b088a3973b86c94f7743fe185197cb5d60c680281184f
5
5
  SHA512:
6
- metadata.gz: 9f07bb37ec67df7e734a007eadbf0d4ac0b2309e893f0b29b139ed6f04984478551cab0c5ac7ed0c02a83ffb4cf82353490aed477ec1e51b26c0a5961c3fa799
7
- data.tar.gz: 8d87c617b0784d10185cbaaa851d2f34c649ce80ede4b82cafdcde1b4bc569f3e2841e514476fc3c95a4fa6eddf67f09e921f0b838dcf6aa45d8d1bedb34ad3b
6
+ metadata.gz: 0da5c33762fdac3b212474c72925c1d47c48b915f7b4a6877a6328b3c07ac15430e0f97c5647d714886d498c94850a1a448100ae29cfd73d7f97abc3bfc2248c
7
+ data.tar.gz: ee26d40ebaa14f0587675692a83d977f6e7eae0ba80c342929847636c9c5509778fa7e19977974deb2421bf7cab0e17a066cafd4188f121d6165347cd1a4b3b4
data/CHANGELOG.md CHANGED
@@ -6,6 +6,30 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.12.1] - 2026-09-21
10
+
11
+ Both of these make code compile that should have compiled, so they ride a
12
+ patch rather than waiting for a minor.
13
+
14
+ ### Fixed
15
+
16
+ - **A pattern in a lambda is checked against the type the caller gives it.**
17
+ Coverage was decided where the pattern was written, which for a lambda is
18
+ before the call that types its parameter — so the subject was still a
19
+ variable and anything but a binding or a wildcard was reported as
20
+ non-exhaustive. `(a, b) = p` inside a `List.map` is the common shape;
21
+ `Tuple.first` / `Tuple.second` or a one-armed `case` were the workarounds.
22
+ Coverage and redundancy now run once inference has finished.
23
+
24
+ ### Added
25
+
26
+ - **`Show` renders a `Dict` and a `Set`.** Both already had `Eq`, so they
27
+ compared but could not be printed, and anything that shows a value on
28
+ failure — a test assertion, a debug line — stopped compiling at
29
+ `Show cannot be derived for Dict(String, Int)`. Keys, values and elements
30
+ render through their own instances, so a `String` key keeps its quotes and a
31
+ nested list prints as a list.
32
+
9
33
  ## [0.12.0] - 2026-09-17
10
34
 
11
35
  ### Breaking
@@ -178,6 +178,8 @@ module Jade
178
178
  # Bodies that don't fit a single Ruby expression.
179
179
  NO_INLINE = %w[
180
180
  Number.checked
181
+ Dict.show_with
182
+ Set.show_with
181
183
  List.sort_with
182
184
  List.sort_by_with
183
185
  List.filter_map
@@ -24,6 +24,8 @@ module Jade
24
24
  # reason. Contained because derivation is private — see
25
25
  # vault jade/plans/jade-test-runner.md for the four layers involved.
26
26
  LIST = 'List.List'
27
+ DICT = 'Dict.Dict'
28
+ SET = 'Set.Set'
27
29
 
28
30
  def special_case(constraint, lookup)
29
31
  case constraint.type
@@ -33,6 +35,12 @@ module Jade
33
35
  in Type::Application(constructor: Type::Constructor(name: LIST), args: [inner])
34
36
  list_show(constraint, inner, lookup)
35
37
 
38
+ in Type::Application(constructor: Type::Constructor(name: DICT), args: [key, value])
39
+ container_show(constraint, [key, value], 'Dict.show_with', lookup)
40
+
41
+ in Type::Application(constructor: Type::Constructor(name: SET), args: [inner])
42
+ container_show(constraint, [inner], 'Set.show_with', lookup)
43
+
36
44
  else
37
45
  nil
38
46
  end
@@ -57,6 +65,22 @@ module Jade
57
65
  end
58
66
  end
59
67
 
68
+ # Threads each element's own `show` into the stdlib function that
69
+ # renders the container.
70
+ def container_show(constraint, inners, fn, lookup)
71
+ inners
72
+ .map { lookup.call(Type.constraint(INTERFACE, it, constraint.origin)) }
73
+ .then { Results.sequence(it) }
74
+ .and_then do |deps|
75
+ args = deps.each_index.map { [:impl_arg, it, 'show'] } + [[:var, 'value']]
76
+
77
+ Symbol::DerivedFunction
78
+ .new(params: ['value'], body: [:call, [:stdlib_fn, fn], args])
79
+ .then { Ok[implementation(constraint, { 'show' => it }, deps:)] }
80
+ end
81
+ end
82
+
83
+
60
84
  def constant(text)
61
85
  Symbol::DerivedFunction.new(params: ['value'], body: text)
62
86
  end
@@ -33,9 +33,8 @@ module Jade
33
33
  [pattern_state, expr_result.constraints]
34
34
  end
35
35
 
36
- PatternAnalysis::Exhaustiveness
37
- .assert([pattern], pattern.range, final_state.env, expr_result.type)
38
- .then { final_state.add_errors(it) }
36
+ final_state
37
+ .defer_patterns([pattern], pattern.range, expr_result.type)
39
38
  .unify_result(
40
39
  expr_result.with(constraints: residual_cs),
41
40
  expected.type,
@@ -86,12 +86,7 @@ module Jade
86
86
  patterns = branches.map(&:pattern)
87
87
  type = result.apply(state.env.substitution).type
88
88
 
89
- [
90
- PatternAnalysis::Exhaustiveness.assert(patterns, node.range, state.env, type),
91
- PatternAnalysis::Redundancy.assert(patterns, state.env, type),
92
- ]
93
- .flatten
94
- .then { state.add_errors(it) }
89
+ state.defer_patterns(patterns, node.range, type)
95
90
  end
96
91
 
97
92
  def mistyped_patterns?(node, state)
@@ -54,10 +54,7 @@ module Jade
54
54
  in AST::Pattern::Binding | AST::Pattern::Wildcard
55
55
  acc
56
56
  else
57
- concrete_t = body_state.env.substitution.apply(t)
58
- PatternAnalysis::Exhaustiveness
59
- .assert([p], p.range, acc.env, concrete_t)
60
- .then { acc.add_errors(it) }
57
+ acc.defer_patterns([p], p.range, t)
61
58
  end
62
59
  end
63
60
 
@@ -0,0 +1,30 @@
1
+ module Jade
2
+ module Frontend
3
+ module TypeChecking
4
+ # The coverage and redundancy of every `case` in a definition, decided
5
+ # against the substitution inference ended with rather than the one in
6
+ # scope where the patterns were written.
7
+ module PatternChecks
8
+ extend self
9
+
10
+ def run(state)
11
+ state
12
+ .pattern_checks
13
+ .flat_map { |(patterns, range, type)| errors_for(patterns, range, type, state.env) }
14
+ .then { state.add_errors(it) }
15
+ end
16
+
17
+ private
18
+
19
+ def errors_for(patterns, range, type, env)
20
+ resolved = env.substitution.apply(type)
21
+
22
+ [
23
+ PatternAnalysis::Exhaustiveness.assert(patterns, range, env, resolved),
24
+ PatternAnalysis::Redundancy.assert(patterns, env, resolved),
25
+ ].flatten
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,9 +1,16 @@
1
1
  module Jade
2
2
  module Frontend
3
3
  module TypeChecking
4
- State = Data.define(:env, :errors, :skip_constraints, :impl_requirements) do
4
+ State = Data.define(:env, :errors, :skip_constraints, :impl_requirements, :pattern_checks) do
5
5
  def self.init(env, skip_constraints: false)
6
- new(env, [], skip_constraints, {})
6
+ new(env, [], skip_constraints, {}, [])
7
+ end
8
+
9
+ # Coverage is decided once inference is done: a pattern in a lambda is
10
+ # checked before the call that gives the lambda's parameter its type,
11
+ # so the subject can still be a variable here.
12
+ def defer_patterns(patterns, range, type)
13
+ with(pattern_checks: pattern_checks + [[patterns, range, type]])
7
14
  end
8
15
 
9
16
  def require_impl(key, constraints)
@@ -8,6 +8,7 @@ require 'jade/frontend/type_checking/expected'
8
8
  require 'jade/frontend/type_checking/inference'
9
9
  require 'jade/frontend/type_checking/loader'
10
10
  require 'jade/frontend/type_checking/narrowing'
11
+ require 'jade/frontend/type_checking/pattern_checks'
11
12
  require 'jade/frontend/type_checking/port_resolution'
12
13
  require 'jade/frontend/type_checking/requirements'
13
14
  require 'jade/frontend/type_checking/result'
@@ -28,7 +29,7 @@ module Jade
28
29
  .load(entry, registry)
29
30
  .then { collect_constraints(entry, registry, it) }
30
31
  .then { check_node(entry.ast, registry, State.init(it), Expected.infer(it.fresh)) }
31
- .then { |state, _| Requirements.reconcile(entry, registry, state) }
32
+ .then { |state, _| Requirements.reconcile(entry, registry, PatternChecks.run(state)) }
32
33
  .then { |amended, state| complete(amended, state, registry) }
33
34
  .and_then { PortResolution.resolve(it, registry) }
34
35
  end
@@ -69,6 +69,18 @@ module Jade
69
69
  end
70
70
  end
71
71
 
72
+ function(
73
+ :show_with,
74
+ { show_key: 'k -> String', show_value: 'v -> String', dict: 'Dict(k, v)' },
75
+ 'String',
76
+ private: true,
77
+ ) do |show_key, show_value, dict|
78
+ dict.hash
79
+ .map { |k, v| "#{show_key.call(k)}: #{show_value.call(v)}" }
80
+ .join(', ')
81
+ .then { "Dict(#{it})" }
82
+ end
83
+
72
84
  function(:keys, { dict: 'Dict(k, v)' }, 'List(k)')
73
85
  function(:values, { dict: 'Dict(k, v)' }, 'List(v)')
74
86
  function(:to_list, { dict: 'Dict(k, v)' }, 'List(Tuple2(k, v))')
@@ -24,6 +24,18 @@ module Jade
24
24
  constraints: [['Basics.Eq', 'a']],
25
25
  )
26
26
 
27
+ function(
28
+ :show_with,
29
+ { show_value: 'a -> String', set: 'Set(a)' },
30
+ 'String',
31
+ private: true,
32
+ ) do |show_value, set|
33
+ set.hash.keys
34
+ .map { show_value.call(it) }
35
+ .join(', ')
36
+ .then { "Set(#{it})" }
37
+ end
38
+
27
39
  function(:"empty?", { set: 'Set(a)' }, 'Bool')
28
40
  function(:size, { set: 'Set(a)' }, 'Int')
29
41
 
data/lib/jade/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Jade
2
- VERSION = '0.12.0'
2
+ VERSION = '0.12.1'
3
3
  end
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.12.0
4
+ version: 0.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Agustin Cornu
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-09-17 00:00:00.000000000 Z
10
+ date: 2026-09-21 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64
@@ -315,6 +315,7 @@ files:
315
315
  - lib/jade/frontend/type_checking/instantiation.rb
316
316
  - lib/jade/frontend/type_checking/loader.rb
317
317
  - lib/jade/frontend/type_checking/narrowing.rb
318
+ - lib/jade/frontend/type_checking/pattern_checks.rb
318
319
  - lib/jade/frontend/type_checking/placeholder.rb
319
320
  - lib/jade/frontend/type_checking/port_resolution.rb
320
321
  - lib/jade/frontend/type_checking/requirements.rb