Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/10964-nbc-order-test-coverage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Cover the enabled `PERRY_L14_NBC_ORDER` path with a two-receiver numeric-field intersection test, while keeping the disabled path independently verified.
5 changes: 4 additions & 1 deletion crates/perry-codegen/src/collectors/hir_facts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,10 @@ pub(crate) fn collect_type_facts(
// `shape_proven_ptr_locals` yields empty inputs below and the fixpoint then
// computes exactly what it computed before.
let (nbc_shape_members, nbc_shape_numeric_fields) =
super::number_by_construction::shape_numeric_inputs(&shape_proven_ptr_locals);
super::number_by_construction::shape_numeric_inputs(
&shape_proven_ptr_locals,
super::number_by_construction::nbc_order_enabled(),
);
let number_by_construction_locals = super::collect_number_by_construction_locals(
stmts,
params,
Expand Down
91 changes: 90 additions & 1 deletion crates/perry-codegen/src/collectors/number_by_construction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -790,10 +790,13 @@ pub(crate) fn nbc_order_enabled() -> bool {
///
/// A union would be a WRONG ANSWER, not a weaker one: `a` numeric on `C` and
/// not on `D` would license a bare `fadd` on `D.a`.
/// The gate is passed in so both modes can be tested without changing the
/// process environment shared by parallel unit tests.
pub(crate) fn shape_numeric_inputs(
shape_proven: &HashMap<u32, crate::collectors::ptr_shape::PtrShapeLocal>,
enabled: bool,
) -> (HashSet<u32>, HashSet<String>) {
if !nbc_order_enabled() || shape_proven.is_empty() {
if !enabled || shape_proven.is_empty() {
return (HashSet::new(), HashSet::new());
}
let mut members: HashSet<u32> = HashSet::new();
Expand All @@ -814,3 +817,89 @@ pub(crate) fn shape_numeric_inputs(
}
(members, fields)
}

#[cfg(test)]
mod shape_input_tests {
use super::*;
use crate::collectors::ptr_shape::PtrShapeLocal;

fn property_local(id: u32, receiver: u32, property: &str) -> Stmt {
Stmt::Let {
id,
name: format!("value_{id}"),
ty: HirType::Any,
mutable: false,
init: Some(Expr::PropertyGet {
object: Box::new(Expr::LocalGet(receiver)),
property: property.to_string(),
byte_offset: 0,
}),
}
}

fn numeric_locals(
stmts: &[Stmt],
members: &HashSet<u32>,
fields: &HashSet<String>,
) -> HashSet<u32> {
super::super::ptr_shape::collect_numeric_by_construction_locals_for_type_analysis(
stmts,
&HashSet::new(),
&HashMap::new(),
&HashSet::new(),
&HashMap::new(),
&HashSet::new(),
members,
fields,
)
}

#[test]
fn nbc_order_intersects_numeric_fields_before_proving_property_locals() {
let shape_proven = HashMap::from([
(
10,
PtrShapeLocal {
class_name: "First".to_string(),
numeric_fields: HashSet::from(["shared".to_string(), "first_only".to_string()]),
report_name: None,
},
),
(
11,
PtrShapeLocal {
class_name: "Second".to_string(),
numeric_fields: HashSet::from(["shared".to_string()]),
report_name: None,
},
),
]);
let stmts = [
property_local(20, 10, "shared"),
property_local(21, 11, "shared"),
property_local(22, 11, "first_only"),
property_local(23, 10, "first_only"),
property_local(24, 12, "shared"),
];

let (members, fields) = shape_numeric_inputs(&shape_proven, true);
let numeric = numeric_locals(&stmts, &members, &fields);
assert!(numeric.contains(&20), "shared field on First is numeric");
assert!(numeric.contains(&21), "shared field on Second is numeric");
assert!(
!numeric.contains(&22),
"union would unsoundly admit Second.first_only"
);
assert!(
!numeric.contains(&23),
"function-wide inputs must be safe for both receivers"
);
assert!(
!numeric.contains(&24),
"an unproven receiver is not a numeric input"
);

let (off_members, off_fields) = shape_numeric_inputs(&shape_proven, false);
assert!(numeric_locals(&stmts, &off_members, &off_fields).is_empty());
}
}
Loading