Skip to content

Commit e9a06b1

Browse files
committed
docs: codify unsigned widening boundaries
1 parent 65165d3 commit e9a06b1

5 files changed

Lines changed: 175 additions & 0 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
- Allow direct lifecycle operations to be authorized by kernel-effect
2626
equivalents: `transfer` accepts `replace + relock`, and `destroy` accepts
2727
`consume + burn`.
28+
- Define expression-local unsigned widening: primitive unsigned integers may
29+
widen inside arithmetic and numeric comparison only; integer literals may be
30+
context-typed, but non-literal values do not widen across assignment, return,
31+
ABI, witness, layout, field initialization, or serialization boundaries.
2832
- Convert canonical bundled examples, language examples, README examples, wiki
2933
tutorials, and release gates to strict 0.15 kernel-effect capabilities.
3034
- Extend strict acceptance and syntax-combination gates so bundled examples

‎docs/CELLSCRIPT_CKB_LANGUAGE_AUDIT.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ literals, explicit `protected` / `witness` / `require` lock syntax, and
4040
fixed-width `lock_args` binding. It does not add implicit signer authority,
4141
hidden sighash defaults, or automatic signature verification.
4242

43+
The 2026-05-18 coercion hardening keeps numeric convenience inside verifier
44+
expressions while preserving explicit boundaries. The rule is named
45+
expression-local unsigned widening: primitive unsigned integers may widen only
46+
inside arithmetic and numeric comparison. Assignment, return, ABI, witness,
47+
Molecule/layout, struct field initialization, and serialization boundaries
48+
remain exact-type boundaries unless the source contains an explicit cast.
49+
Integer literals may still be context-typed by an expected primitive integer
50+
type, but non-literal values do not pretend to have another width at a boundary.
51+
4352
The 0.13 compiler also exposes CKB-specific evidence instead of hiding it behind
4453
a generic artifact:
4554

‎docs/wiki/Tutorial-02-Language-Basics.md‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,39 @@ Hash
9494
Use fixed-size byte arrays when a value must live in a predictable persistent
9595
schema or CKB data layout.
9696

97+
### Expression-local Unsigned Widening
98+
99+
CellScript supports expression-local primitive unsigned integer widening for
100+
arithmetic and numeric comparison:
101+
102+
```cellscript
103+
let total: u64 = amount_u64 + fee_u16
104+
let under_limit: bool = fee_u16 < amount_u64
105+
```
106+
107+
The chain is `u8 -> u16 -> u32 -> u64 -> u128`, but the widening is local to
108+
the expression being evaluated. It does not cross assignment, return, ABI,
109+
witness, `create` layout, struct field initialization, or serialization
110+
boundaries.
111+
112+
Integer literals may be context-typed by an expected primitive integer type:
113+
114+
```cellscript
115+
let byte: u8 = 1
116+
```
117+
118+
Non-literal numeric values must keep their actual width at boundaries:
119+
120+
```cellscript
121+
let amount64: u64 = amount16 // rejected
122+
let explicit: u64 = amount16 as u64 // accepted
123+
```
124+
125+
Compound assignment is a write boundary. `target += rhs` is valid only when
126+
`rhs` is the same width as, or narrower than, `target`. Generic `u128`
127+
arithmetic and ordering remain unsupported except for explicitly implemented
128+
`u128` delta or equality paths.
129+
97130
`Signature` is not a built-in scalar. If a contract needs to carry a signature,
98131
model it explicitly:
99132

‎docs/wiki/Tutorial-11-Scoped-Invariants-and-ProofPlan.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,25 @@ When some related action origins still lack matching evidence, ProofPlan reports
243243
`declared(unmatched_related_action_obligation_count:...)` so reviewers do not
244244
mistake one matching action for exhaustive action coverage.
245245

246+
## Numeric Boundary Evidence
247+
248+
ProofPlan and release review should treat integer width changes at verifier
249+
boundaries as explicit evidence. The language rule is expression-local unsigned
250+
widening: `u8`, `u16`, `u32`, `u64`, and `u128` values may widen inside
251+
arithmetic and numeric comparison, but not across assignment, return, ABI,
252+
witness, layout, struct field initialization, or serialization boundaries.
253+
254+
For boundary values, the source should show intent:
255+
256+
```cellscript
257+
receipt.amount = value as u64
258+
```
259+
260+
Do not read acceptance of mixed-width arithmetic as acceptance of implicit
261+
numeric coercions throughout the language. Non-literal boundary conversions
262+
must stay explicit so reviewers can see which transaction fields, witness
263+
values, and serialized layouts changed width.
264+
246265
## JSON Output
247266

248267
For tooling, use:

‎src/types/mod.rs‎

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6553,6 +6553,116 @@ where
65536553
assert!(err.message.contains("matching types") || err.message.contains("u128"), "u64==u128 rejected: {}", err.message);
65546554
}
65556555

6556+
#[test]
6557+
fn expected_type_does_not_widen_non_literal_let_boundary() {
6558+
let err = check(&source_module(
6559+
r#"
6560+
module types::expected_let_no_widen
6561+
6562+
action bad(x: u16) -> u64
6563+
where
6564+
let y: u64 = x
6565+
return y
6566+
"#,
6567+
))
6568+
.unwrap_err();
6569+
assert!(
6570+
err.message.contains("type mismatch") || err.message.contains("mismatch"),
6571+
"let expected type should not widen a non-literal u16 into u64: {}",
6572+
err.message
6573+
);
6574+
}
6575+
6576+
#[test]
6577+
fn expected_type_does_not_widen_non_literal_return_boundary() {
6578+
let err = check(&source_module(
6579+
r#"
6580+
module types::expected_return_no_widen
6581+
6582+
action bad(x: u16) -> u64
6583+
where
6584+
return x
6585+
"#,
6586+
))
6587+
.unwrap_err();
6588+
assert!(
6589+
err.message.contains("return type") || err.message.contains("mismatch"),
6590+
"return expected type should not widen a non-literal u16 into u64: {}",
6591+
err.message
6592+
);
6593+
}
6594+
6595+
#[test]
6596+
fn expected_type_does_not_widen_non_literal_abi_arg_boundary() {
6597+
let err = check(&source_module(
6598+
r#"
6599+
module types::expected_abi_no_widen
6600+
6601+
fn takes_u64(x: u64) -> u64 {
6602+
return x
6603+
}
6604+
6605+
action bad(x: u16) -> u64
6606+
where
6607+
return takes_u64(x)
6608+
"#,
6609+
))
6610+
.unwrap_err();
6611+
assert!(
6612+
err.message.contains("argument 1 type mismatch"),
6613+
"ABI argument expected type should not widen a non-literal u16 into u64: {}",
6614+
err.message
6615+
);
6616+
}
6617+
6618+
#[test]
6619+
fn expected_type_does_not_widen_non_literal_field_boundary() {
6620+
let err = check(&source_module(
6621+
r#"
6622+
module types::expected_field_no_widen
6623+
6624+
struct Holder {
6625+
value: u64,
6626+
}
6627+
6628+
action bad(x: u16) -> u64
6629+
where
6630+
let holder = Holder { value: x }
6631+
return holder.value
6632+
"#,
6633+
))
6634+
.unwrap_err();
6635+
assert!(
6636+
err.message.contains("type mismatch"),
6637+
"field expected type should not widen a non-literal u16 into u64: {}",
6638+
err.message
6639+
);
6640+
}
6641+
6642+
#[test]
6643+
fn explicit_cast_can_cross_integer_width_boundary() {
6644+
check(&source_module(
6645+
r#"
6646+
module types::explicit_cast_boundary
6647+
6648+
struct Holder {
6649+
value: u64,
6650+
}
6651+
6652+
fn takes_u64(x: u64) -> u64 {
6653+
return x
6654+
}
6655+
6656+
action good(x: u16) -> u64
6657+
where
6658+
let y: u64 = x as u64
6659+
let holder = Holder { value: x as u64 }
6660+
return takes_u64(y) + holder.value
6661+
"#,
6662+
))
6663+
.expect("explicit casts should be accepted across integer width boundaries");
6664+
}
6665+
65566666
#[test]
65576667
fn contextual_integer_literals_fit_declared_widths() {
65586668
let module = source_module(

0 commit comments

Comments
 (0)