use super::*;
const RULE: &str = "prefer-pick";
fn lint_source(source: &str) -> Vec<LintDiagnostic> {
let tokens = Lexer::new(source).tokenize().unwrap();
let program = Parser::new(tokens).parse().unwrap();
let diagnostics = harn_parser::TypeChecker::new()
.check_with_facts(&program, source)
.diagnostics;
lint_diagnostics_from_type_diagnostics(&diagnostics, &[])
}
#[test]
fn harness_capability_record_becomes_pick_and_keeps_behavior() {
let source = r#"
type Context = {env: HarnessEnv, fs: HarnessFs}
fn config_exists(ctx: Context) -> bool {
return ctx.fs.exists(ctx.env.get_or("PREFER_PICK_TEST_MISSING", "."))
}
fn main(harness: Harness) {
const ctx = {env: harness.env, fs: harness.fs}
assert_eq(config_exists(ctx), true)
harness.stdio.println("pass")
}
"#;
let diagnostics = lint_source(source);
assert_eq!(count_rule(&diagnostics, RULE), 1, "{diagnostics:?}");
assert_eq!(execute_strict_source(source), "pass");
let fixed = apply_fixes(source, &diagnostics);
assert!(
fixed.contains(r#"const ctx = pick(harness, ["env", "fs"])"#),
"{fixed}"
);
assert_eq!(execute_strict_source(&fixed), "pass");
assert!(!has_rule(&lint_source(&fixed), RULE));
}
#[test]
fn typed_record_parameter_and_struct_value_are_rewritten() {
let source = r#"
type Person = {name: string, age: int}
struct Point { x: int, y: int }
fn describe(person: Person, point: Point) -> string {
const who = {name: person.name, age: person.age}
const at = {x: point.x, y: point.y}
return "${who.name}:${who.age}@${at.x},${at.y}"
}
fn main(harness: Harness) {
assert_eq(describe({name: "Ada", age: 37}, Point {x: 1, y: 2}), "Ada:37@1,2")
harness.stdio.println("pass")
}
"#;
let diagnostics = lint_source(source);
assert_eq!(count_rule(&diagnostics, RULE), 2, "{diagnostics:?}");
assert_eq!(execute_strict_source(source), "pass");
let fixed = apply_fixes(source, &diagnostics);
assert!(
fixed.contains(r#"pick(person, ["name", "age"])"#),
"{fixed}"
);
assert!(fixed.contains(r#"pick(point, ["x", "y"])"#), "{fixed}");
assert_eq!(execute_strict_source(&fixed), "pass");
}
#[test]
fn inferred_record_binding_is_rewritten() {
let source = r#"
fn main(harness: Harness) {
const source = {name: "Ada", age: 37, city: "Paris"}
const copy = {name: source.name, city: source.city}
harness.stdio.println(copy.name)
}
"#;
let diagnostics = lint_source(source);
assert_eq!(count_rule(&diagnostics, RULE), 1, "{diagnostics:?}");
let fixed = apply_fixes(source, &diagnostics);
assert!(
fixed.contains(r#"pick(source, ["name", "city"])"#),
"{fixed}"
);
}
#[test]
fn stays_silent_when_a_missing_key_would_change_the_result() {
for source in [
r"
fn copy(settings: dict<string, int>) -> dict<string, int> {
return {retries: settings.retries, timeout: settings.timeout}
}
",
r"
type Person = {name: string, age?: int}
fn copy(person: Person) { return {name: person.name, age: person.age} }
",
r#"
import { Settings } from "./settings"
fn copy(settings: Settings) { return {a: settings.a, b: settings.b} }
"#,
] {
let diagnostics = lint_source(source);
assert!(!has_rule(&diagnostics, RULE), "{source}\n{diagnostics:?}");
}
}
#[test]
fn stays_silent_when_the_literal_is_not_a_plain_projection() {
for source in [
"fn main(harness: Harness) { const ctx = {env: harness.env} }",
"fn main(harness: Harness) { const ctx = {environment: harness.env, fs: harness.fs} }",
"fn main(harness: Harness, other: Harness) { const ctx = {env: harness.env, fs: other.fs} }",
"fn main(harness: Harness) { const ctx = {env: harness.env, fs: harness.fs, n: 1} }",
r"
type Person = {name: string, age: int}
fn copy(person: Person?) { return {name: person?.name, age: person?.age} }
",
"fn main(harness: Harness) { const ctx = {env: harness.env, nope: harness.nope} }",
"fn helper(harness: any) { return {env: harness.env, fs: harness.fs} }",
] {
let diagnostics = lint_source(source);
assert!(!has_rule(&diagnostics, RULE), "{source}\n{diagnostics:?}");
}
}
#[test]
fn stays_silent_when_pick_is_shadowed() {
let source = r"
fn pick(value: int) -> int { return value }
fn main(harness: Harness) {
const ctx = {env: harness.env, fs: harness.fs}
harness.stdio.println(pick(1))
}
";
let diagnostics = lint_source(source);
assert!(!has_rule(&diagnostics, RULE), "{diagnostics:?}");
}
#[test]
fn repair_is_behavior_preserving_and_machine_applicable() {
let source = "fn main(harness: Harness) { const ctx = {env: harness.env, fs: harness.fs} }";
let diagnostics = lint_source(source);
let diagnostic = diagnostics
.iter()
.find(|diagnostic| diagnostic.rule == RULE)
.expect("prefer-pick fires");
let repair = diagnostic.repair().expect("repair template");
assert_eq!(repair.id.as_str(), "records/pick-fields");
assert!(repair.safety.is_machine_applicable());
assert!(diagnostic.machine_applicable_fix().is_some());
}
#[test]
fn aliases_unions_nested_records_and_contextual_results_use_checker_types() {
for source in [
r"
type Person = {name: string, age: int}
type Alias = Person
fn copy(person: Alias) -> Person { return {name: person.name, age: person.age} }
",
r#"
type Choice = {kind: "a", value: string} | {kind: "b", value: int}
fn copy(choice: Choice) { return {kind: choice.kind, value: choice.value} }
"#,
r"
type Person = {name: string, age: int}
fn copy(wrapper: {person: Person}) -> Person {
return {name: wrapper.person.name, age: wrapper.person.age}
}
",
r"
struct Pair<A, B> { first: A, second: B }
fn copy(pair: Pair<int, string>) -> {first: int, second: string} {
const result: {first: int, second: string} = {first: pair.first, second: pair.second}
return result
}
",
r"
fn copy(source: {left: tuple<int, string>, right: tuple<int, string>}) -> {left: tuple<int, string>, right: tuple<int, string>} {
return {left: source.left, right: source.right}
}
",
] {
let diagnostics = lint_source(source);
assert_eq!(
count_rule(&diagnostics, RULE),
1,
"{source}\n{diagnostics:?}"
);
let fixed = apply_fixes(source, &diagnostics);
assert!(!has_rule(&lint_source(&fixed), RULE), "{fixed}");
}
}
#[test]
fn shadowing_mutation_missing_fields_and_comments_prevent_unsafe_rewrites() {
for source in [
r"
fn copy(source: {a: int, b: int}) {
const shadow = {source -> {a: source.a, b: source.b}}
return shadow({})
}
",
r"
fn copy(source: {a: int, b: int}) {
fn inner(pick: any) { return {a: source.a, b: source.b} }
return inner(nil)
}
",
r"
fn copy(source: {nested?: {a: int, b: int}}) {
return {a: source.nested.a, b: source.nested.b}
}
",
r"
fn copy(source: {a: int, b: int} | {a: int, b?: int}) {
return {a: source.a, b: source.b}
}
",
r"
fn copy() {
let source: dict<string, int> = {a: 1, b: 2}
source = {}
return {a: source.a, b: source.b}
}
",
r"
fn copy(source: {a: int, b: int}) {
return {a: source.a, /* Keep the second value. */ b: source.b}
}
",
r"
fn next() -> {a: int, b: int} { return {a: 1, b: 2} }
fn copy() { return {a: next().a, b: next().b} }
",
] {
let diagnostics = lint_source(source);
assert!(!has_rule(&diagnostics, RULE), "{source}\n{diagnostics:?}");
}
}