Skip to content
Merged
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
6 changes: 5 additions & 1 deletion crates/compiler/src/lowering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2855,7 +2855,7 @@ impl LoweringContext {
/// the first of those appears. Kept are the entry points and what
/// is entered some other way or does work at lowering: externs and
/// bodiless declarations, fiber and async bodies, generics,
/// functions with effects or handlers, and a name declared twice,
/// functions with effects or handlers, and a name given two bodies,
/// whose meaning depends on the order its declarations are built.
///
/// `ZYNTAX_DISABLE_REACH_LOWERING=1` builds every own function up
Expand All @@ -2868,10 +2868,14 @@ impl LoweringContext {
if !self.config.closed || std::env::var_os("ZYNTAX_DISABLE_REACH_LOWERING").is_some() {
return deferred;
}
// Only bodies count: a body beside a bodiless declaration of its
// name, as a hook the program defines for a library's extern, has
// one meaning, and the extern stands for it until it is built.
let mut seen = std::collections::HashSet::new();
let mut twice = std::collections::HashSet::new();
for decl in &program.declarations {
if let TypedDeclaration::Function(f) = &decl.node
&& f.body.is_some()
&& !seen.insert(f.name)
{
twice.insert(f.name);
Expand Down
34 changes: 34 additions & 0 deletions crates/zyntax_python/tests/reach_lowering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,37 @@ fn a_closed_program_builds_only_what_its_body_reaches() {
let open = compiled(UNREACHED, false);
assert!(open.iter().any(|n| n == "unused"), "{open:?}");
}

const TYPED_OPERATOR: &str = r#"
class Vec3:
def __init__(self, x: float, y: float, z: float):
self.x = x
self.y = y
self.z = z

def __add__(self, other):
return Vec3(self.x + other.x, self.y + other.y, self.z + other.z)

def main() -> int:
a = Vec3(1.0, 2.0, 3.0)
acc = Vec3(0.0, 0.0, 0.0)
for i in range(10):
acc = acc + a
return int(acc.x + acc.y + acc.z)

print(main())
"#;

/// A class's operator is reached only through its typed instances, so
/// the hook that dispatches a dynamic `a + b` to it is never called and
/// neither it nor the dynamic arithmetic it serves is built.
#[test]
fn an_operator_used_on_typed_instances_builds_no_dynamic_arithmetic() {
let built = compiled(TYPED_OPERATOR, true);
for name in ["zb_hook_instance_arith", "zb_any_arith"] {
assert!(
!built.iter().any(|n| n == name),
"{name} is built for a program that never boxes an instance"
);
}
}
Loading