Skip to content

Commit b3b60a2

Browse files
fix: narrow nullable scalar switches once
A nullable scalar switch could become an outer null check followed by a second Option match. The second match kept a dead Null Access throw in generated Rust. Recognize the same compiler-generated alias shape when its final local sits inside transparent one-item blocks. Emit one Option match, while keeping all existing source-local, type, and Copy-value checks. The portable and metal snapshots cover direct Int and Float switches plus a called try-and-close flow that produces the nested alias shape. Focused generation, formatting, Cargo build, Clippy, snapshot comparison, and the corrected codex-hxrust consumer replay pass. The full harness passed all guards and 31 semantic-difference cases. Six unrelated semantic-difference cases exceeded the fixed 180-second timeout on the contended host.
1 parent 83333c9 commit b3b60a2

6 files changed

Lines changed: 107 additions & 3 deletions

File tree

docs/rust-raw-authority-inventory.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
},
7272
{
7373
"path": "src/reflaxe/rust/RustCompiler.hx",
74-
"line": 17483,
74+
"line": 17491,
7575
"enclosingFunction": "addSpec",
7676
"factory": "traitImplementationAt",
7777
"authorityId": "metadata-owned",

scripts/lint/dynamic_allowlist.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ src/reflaxe/rust/RustCompiler.hx:2814
1717
src/reflaxe/rust/RustCompiler.hx:10839
1818
src/reflaxe/rust/RustCompiler.hx:13641
1919
src/reflaxe/rust/RustCompiler.hx:13660
20-
src/reflaxe/rust/RustCompiler.hx:19972
20+
src/reflaxe/rust/RustCompiler.hx:19980
2121
src/reflaxe/rust/RustSourceMap.hx:419
2222
src/reflaxe/rust/analyze/RepresentationAnalysisSnapshot.hx:77
2323
src/reflaxe/rust/analyze/RepresentationAnalysisSnapshot.hx:81

src/reflaxe/rust/RustCompiler.hx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14205,6 +14205,7 @@ class RustCompiler extends GenericCompiler<RustFile, RustFile, RustExpr, RustFil
1420514205

1420614206
How
1420714207
- Requires the null comparison, alias initializer, and alias tail to name the same Haxe local.
14208+
- Peels only metadata, parentheses, and transparent singleton blocks around the alias tail.
1420814209
- Requires a Copy inner type and the exact non-null Rust result type, then compiles the source once.
1420914210
- Returns `null` for every other conditional so ordinary Haxe null behavior stays unchanged.
1421014211
**/
@@ -14259,7 +14260,14 @@ class RustCompiler extends GenericCompiler<RustFile, RustFile, RustExpr, RustFil
1425914260
case TLocal(variable): variable.id == comparedLocal.id;
1426014261
case _: false;
1426114262
};
14262-
var tailMatches = switch (unwrapMetaParen(expressions[1]).expr) {
14263+
function unwrapAliasTail(expression:TypedExpr):TypedExpr {
14264+
var current = unwrapMetaParen(expression);
14265+
return switch (current.expr) {
14266+
case TBlock(items) if (items.length == 1): unwrapAliasTail(items[0]);
14267+
case _: current;
14268+
};
14269+
}
14270+
var tailMatches = switch (unwrapAliasTail(expressions[1]).expr) {
1426314271
case TLocal(variable): variable.id == alias.id;
1426414272
case _: false;
1426514273
};

test/snapshot/nullable_scalar_switch_narrowing/Main.hx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,24 @@
11
class Main {
2+
static function readExit(value:Null<Int>):Null<Int> {
3+
return value;
4+
}
5+
6+
static function closeSource():Void {}
7+
8+
static function narrowRead(value:Null<Int>):Int {
9+
try {
10+
final exit = readExit(value);
11+
closeSource();
12+
final concreteExit:Int = switch (exit) {
13+
case null: -1;
14+
case concrete: concrete;
15+
}
16+
return concreteExit;
17+
} catch (_:haxe.Exception) {
18+
return -2;
19+
}
20+
}
21+
222
static function narrow(value:Null<Int>):Int {
323
return switch (value) {
424
case null: -1;
@@ -14,6 +34,8 @@ class Main {
1434
}
1535

1636
static function main():Void {
37+
if (narrowRead(9) != 9) throw "read narrowing failed";
38+
if (narrowRead(null) != -1) throw "null read narrowing failed";
1739
if (narrow(7) != 7) throw "non-null narrowing failed";
1840
if (narrow(null) != -1) throw "null narrowing failed";
1941
if (narrowFloat(2.5) != 2.5) throw "non-null Float narrowing failed";

test/snapshot/nullable_scalar_switch_narrowing/intended/src/main.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,34 @@ pub(crate) fn __hx_is_subtype_type_id(actual: u32, expected: u32) -> bool {
2323
false
2424
}
2525

26+
fn read_exit(value: Option<i32>) -> Option<i32> {
27+
return value;
28+
}
29+
30+
fn close_source() {}
31+
32+
fn narrow_read(value: Option<i32>) -> i32 {
33+
match hxrt::exception::catch_unwind(|| {
34+
let exit: Option<i32> = read_exit(value);
35+
close_source();
36+
let concrete_exit: i32 = match exit {
37+
None => -1,
38+
Some(__hx_value) => __hx_value,
39+
};
40+
return concrete_exit;
41+
}) {
42+
Ok(__hx_ok) => __hx_ok,
43+
Err(__hx_ex) => {
44+
match __hx_ex.downcast::<crate::HxRef<crate::haxe_exception::Exception>>() {
45+
Ok(_) => {
46+
return -2;
47+
}
48+
Err(__hx_ex) => hxrt::exception::rethrow(__hx_ex),
49+
}
50+
}
51+
}
52+
}
53+
2654
fn narrow(value: Option<i32>) -> i32 {
2755
return match value {
2856
None => -1,
@@ -38,6 +66,16 @@ fn narrow_float(value: Option<f64>) -> f64 {
3866
}
3967

4068
fn main() {
69+
if narrow_read(Some(9)) != 9 {
70+
hxrt::exception::throw(hxrt::dynamic::from(hxrt::string::HxString::from(
71+
"read narrowing failed",
72+
)));
73+
}
74+
if narrow_read(None) != -1 {
75+
hxrt::exception::throw(hxrt::dynamic::from(hxrt::string::HxString::from(
76+
"null read narrowing failed",
77+
)));
78+
}
4179
if narrow(Some(7)) != 7 {
4280
hxrt::exception::throw(hxrt::dynamic::from(hxrt::string::HxString::from(
4381
"non-null narrowing failed",

test/snapshot/nullable_scalar_switch_narrowing/intended_metal/src/main.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,34 @@ pub(crate) fn __hx_is_subtype_type_id(actual: u32, expected: u32) -> bool {
2323
false
2424
}
2525

26+
fn read_exit(value: Option<i32>) -> Option<i32> {
27+
return value;
28+
}
29+
30+
fn close_source() {}
31+
32+
fn narrow_read(value: Option<i32>) -> i32 {
33+
match hxrt::exception::catch_unwind(|| {
34+
let exit: Option<i32> = read_exit(value);
35+
close_source();
36+
let concrete_exit: i32 = match exit {
37+
None => -1,
38+
Some(__hx_value) => __hx_value,
39+
};
40+
return concrete_exit;
41+
}) {
42+
Ok(__hx_ok) => __hx_ok,
43+
Err(__hx_ex) => {
44+
match __hx_ex.downcast::<crate::HxRef<crate::haxe_exception::Exception>>() {
45+
Ok(_) => {
46+
return -2;
47+
}
48+
Err(__hx_ex) => hxrt::exception::rethrow(__hx_ex),
49+
}
50+
}
51+
}
52+
}
53+
2654
fn narrow(value: Option<i32>) -> i32 {
2755
return match value {
2856
None => -1,
@@ -38,6 +66,14 @@ fn narrow_float(value: Option<f64>) -> f64 {
3866
}
3967

4068
fn main() {
69+
if narrow_read(Some(9)) != 9 {
70+
hxrt::exception::throw(hxrt::dynamic::from(String::from("read narrowing failed")));
71+
}
72+
if narrow_read(None) != -1 {
73+
hxrt::exception::throw(hxrt::dynamic::from(String::from(
74+
"null read narrowing failed",
75+
)));
76+
}
4177
if narrow(Some(7)) != 7 {
4278
hxrt::exception::throw(hxrt::dynamic::from(String::from(
4379
"non-null narrowing failed",

0 commit comments

Comments
 (0)