#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use brink_analyzer::{AnalysisOptions, Dialect, TypePolicy};
use brink_db::ProjectDb;
use brink_format::DefinitionId;
use brink_ir::DiagnosticCode;
fn strict_native_opts() -> AnalysisOptions {
AnalysisOptions {
dialect: Dialect::Brink,
types: Some(TypePolicy::Strict),
..AnalysisOptions::default()
}
}
const SOURCE: &str = "\
var counter = 7
fn bumped(n: int): int {
let f = |x| x + counter;
return f(n);
}
flow main() {
Bumped: {bumped(3)}
-> END
}
";
fn lifted_lambda_defs(story: &brink_format::StoryData) -> Vec<DefinitionId> {
story
.address_paths
.iter()
.filter(|ap| {
story
.name_table
.get(ap.path.0 as usize)
.is_some_and(|p| p.contains("#lambda-"))
})
.map(|ap| ap.target)
.collect()
}
#[test]
fn a_lifted_lambda_ships_no_effect_row() {
let mut db = ProjectDb::new();
db.set_file("main.brink", SOURCE.to_owned());
db.set_entry("main.brink");
let product = db.story_data().expect("entry is set");
let story = product.story.as_ref().expect("story compiles cleanly");
let lifted = lifted_lambda_defs(story);
assert!(
!lifted.is_empty(),
"the lambda must lift to an addressable container (#1709); \
name_table = {:?}",
story.name_table
);
assert!(
!story.effect_rows.is_empty(),
"the story's knots/stitches do ship rows, so an empty table would \
make the next assertion meaningless"
);
let with_rows: Vec<DefinitionId> = story.effect_rows.iter().map(|r| r.def).collect();
for def in &lifted {
assert!(
!with_rows.contains(def),
"#1680: a lambda-lifted function is expected to carry no \
`EffectRows` entry today — if this now fails, the row table \
reaches lifted lambdas and this characterization must be \
replaced by the real contract"
);
}
}
#[test]
fn the_enclosing_def_absorbs_the_lambda_bodys_atoms() {
let mut db = ProjectDb::new();
db.set_file("main.brink", SOURCE.to_owned());
db.set_entry("main.brink");
let index = db.symbol_index();
let bumped = *index
.by_name
.get("bumped")
.expect("`bumped` is indexed")
.first()
.expect("indexed name has at least one def");
let counter = *index
.by_name
.get("counter")
.expect("`counter` is indexed")
.first()
.expect("indexed name has at least one def");
let product = db.story_data().expect("entry is set");
let story = product.story.as_ref().expect("story compiles cleanly");
let row = story
.effect_rows
.iter()
.find(|r| r.def == bumped)
.expect("`bumped` ships a container row");
assert!(
row.direct.reads.contains(&counter),
"the lambda body's read of `counter` must be absorbed into the \
enclosing def's row — reads = {:?}",
row.direct.reads
);
}
const SOURCE_BLOCK_BODY: &str = "\
var counter = 7
fn bumped_block(n: int): int {
let f = |x|: int {
let a = x + counter;
a
};
return f(n);
}
flow main() {
BumpedBlock: {bumped_block(3)}
-> END
}
";
#[test]
fn the_enclosing_def_absorbs_a_block_bodied_lambdas_stmt_atoms() {
let mut db = ProjectDb::new();
db.set_file("main.brink", SOURCE_BLOCK_BODY.to_owned());
db.set_entry("main.brink");
let index = db.symbol_index();
let bumped = *index
.by_name
.get("bumped_block")
.expect("`bumped_block` is indexed")
.first()
.expect("indexed name has at least one def");
let counter = *index
.by_name
.get("counter")
.expect("`counter` is indexed")
.first()
.expect("indexed name has at least one def");
let product = db.story_data().expect("entry is set");
let story = product.story.as_ref().expect("story compiles cleanly");
let row = story
.effect_rows
.iter()
.find(|r| r.def == bumped)
.expect("`bumped_block` ships a container row");
assert!(
row.direct.reads.contains(&counter),
"the block-bodied lambda's *stmt* read of `counter` (not its tail) \
must be absorbed into the enclosing def's row — reads = {:?}",
row.direct.reads
);
}
const SOURCE_LAMBDA_RETURN_LEAK: &str = "\
fn e150_lambda_return_leak(n: int): int {
let g = || {
return 1;
};
}
";
#[test]
fn a_lambdas_return_does_not_satisfy_the_enclosing_defs_e150_check() {
let mut db = ProjectDb::new();
let file = db.set_file("main.brink", SOURCE_LAMBDA_RETURN_LEAK.to_owned());
db.set_entry("main.brink");
db.set_analysis_options(strict_native_opts());
let diags = db.diagnostics(file).expect("file diagnostics");
assert!(
diags.iter().any(|d| d.code == DiagnosticCode::E150),
"`e150_lambda_return_leak` itself never returns a value — the \
lambda's own `return 1` must not satisfy this def's E150 check: \
{diags:?}"
);
}
const SOURCE_BLOCK_CALL_STMT: &str = "\
var flag = false
fn mark(x: int): int {
flag = true;
return x;
}
fn bumped_call(n: int): int {
let f = |x|: int {
mark(x);
x
};
return f(n);
}
flow main() {
BumpedCall: {bumped_call(3)}
-> END
}
";
#[test]
fn the_enclosing_def_absorbs_a_block_bodied_lambdas_call_stmt_atom() {
let mut db = ProjectDb::new();
db.set_file("main.brink", SOURCE_BLOCK_CALL_STMT.to_owned());
db.set_entry("main.brink");
let index = db.symbol_index();
let bumped_call = *index
.by_name
.get("bumped_call")
.expect("`bumped_call` is indexed")
.first()
.expect("indexed name has at least one def");
let flag = *index
.by_name
.get("flag")
.expect("`flag` is indexed")
.first()
.expect("indexed name has at least one def");
let product = db.story_data().expect("entry is set");
let story = product.story.as_ref().expect("story compiles cleanly");
let row = story
.effect_rows
.iter()
.find(|r| r.def == bumped_call)
.expect("`bumped_call` ships a container row");
assert!(
row.direct.writes.contains(&flag),
"the block-bodied lambda's `mark(x)` call statement (a bare \
ExprStmt, not a TempDecl initializer) must resolve a call-graph \
edge so the fixpoint pulls `mark`'s write of `flag` into the \
enclosing def's row — writes = {:?}",
row.direct.writes
);
}
const SOURCE_OUTER_TEMP_SHADOW: &str = "\
fn shadow(n: int): int {
let a = n + 1;
let g = |x: int|: string {
let a = \"str\";
a
};
return a;
}
";
#[test]
fn a_lambdas_temp_does_not_corrupt_an_outer_same_named_local() {
let mut db = ProjectDb::new();
let file = db.set_file("main.brink", SOURCE_OUTER_TEMP_SHADOW.to_owned());
db.set_entry("main.brink");
db.set_analysis_options(strict_native_opts());
let diags = db.diagnostics(file).expect("file diagnostics");
assert!(
diags.is_empty(),
"the lambda's own `let a = \"str\"` is a separate binding from the \
enclosing `let a = n + 1` — it must not unify into the enclosing \
local and conflict the enclosing def's own return type: {diags:?}"
);
}