mod support;
use scryer_db::{ScryerDb, Symbol, SymbolReference};
use scryer_engine::IndexOptions;
use support::fixture::index_files;
async fn value_lines(db: &ScryerDb, project: u64, name: &str) -> anyhow::Result<Vec<u32>> {
let mut conn = db.lock().await;
let ids: Vec<u64> = Symbol::filter_by_project_id(project)
.exec(&mut *conn)
.await?
.into_iter()
.filter(|s| s.name == name)
.map(|s| s.id)
.collect();
let mut lines: Vec<u32> = SymbolReference::filter_by_project_id(project)
.exec(&mut *conn)
.await?
.into_iter()
.filter(|r| r.role == "value" && ids.contains(&r.symbol_id))
.map(|r| r.line_number)
.collect();
lines.sort_unstable();
Ok(lines)
}
#[tokio::test]
async fn rust_values_respect_shadowing_and_kind() -> anyhow::Result<()> {
let (db, project) = index_files(
&[(
"src/lib.rs",
"pub fn target() -> u32 { 1 }\n\
pub const LIMIT: u32 = 3;\n\
pub fn take(f: fn() -> u32) -> u32 { f() }\n\
pub fn as_argument() -> u32 { take(target) }\n\
pub fn in_let() { let _f: fn() -> u32 = target; }\n\
pub fn returned() -> fn() -> u32 { return target; }\n\
pub fn in_array() { let _a = [target, target]; }\n\
pub fn shadowed_param(target: u32) -> u32 { take_u(target) }\n\
pub fn shadowed_let() { let target = 1; take_u(target); }\n\
pub fn shadowed_closure() { let _ = |target: u32| take_u(target); }\n\
pub fn called() { target(); }\n\
pub fn constant() { take_u(LIMIT); }\n\
pub fn take_u(x: u32) -> u32 { x }\n",
)],
IndexOptions::default(),
)
.await?;
assert_eq!(
value_lines(&db, project, "target").await?,
vec![4, 5, 6, 7, 7]
);
assert!(value_lines(&db, project, "LIMIT").await?.is_empty());
Ok(())
}
#[tokio::test]
async fn python_values_respect_shadowing() -> anyhow::Result<()> {
let (db, project) = index_files(
&[(
"m.py",
"def target():\n return 1\n\n\
def take(f):\n return f\n\n\
def as_argument():\n return take(target)\n\n\
def assigned():\n f = target\n return f\n\n\
def shadowed_param(target):\n return take(target)\n\n\
def shadowed_assign():\n target = 3\n return take(target)\n\n\
def default_value(f=target):\n return f\n\n\
def called():\n return target()\n",
)],
IndexOptions::default(),
)
.await?;
assert_eq!(value_lines(&db, project, "target").await?, vec![8, 11]);
Ok(())
}
#[tokio::test]
async fn names_bound_by_struct_and_destructuring_patterns_are_locals() -> anyhow::Result<()> {
let (db, project) = index_files(
&[
(
"src/lib.rs",
"pub fn path() -> u32 { 1 }\n\
pub fn parent() -> u32 { 2 }\n\
pub struct P { pub path: u32, pub parent: u32 }\n\
pub fn take(x: u32) -> u32 { x }\n\
pub fn in_let(p: P) -> u32 { let P { path, .. } = p; take(path) }\n\
pub fn in_match(p: P) -> u32 { match p { P { parent, .. } => take(parent) } }\n\
pub fn in_if_let(p: Option<P>) -> u32 { if let Some(P { path, .. }) = p { take(path) } else { 0 } }\n",
),
(
"m.ts",
"export function target() { return 1; }\n\
export function take(x: number) { return x; }\n\
export function destructured(o: { target: number }) { const { target } = o; return take(target); }\n",
),
],
IndexOptions::default(),
)
.await?;
assert!(value_lines(&db, project, "path").await?.is_empty());
assert!(value_lines(&db, project, "parent").await?.is_empty());
assert!(value_lines(&db, project, "target").await?.is_empty());
Ok(())
}
#[tokio::test]
async fn a_method_is_not_a_value_and_an_associated_name_is_not_a_function() -> anyhow::Result<()> {
let (db, project) = index_files(
&[(
"src/lib.rs",
"pub struct S;\n\
impl S { pub fn drop(&self) {} pub fn free_name(&self) {} }\n\
pub fn free_name() {}\n\
pub fn take<T>(_f: T) {}\n\
pub fn uses() { take(drop); take(free_name); }\n",
)],
IndexOptions::default(),
)
.await?;
assert!(value_lines(&db, project, "drop").await?.is_empty());
let mut conn = db.lock().await;
let free = Symbol::filter_by_project_id(project)
.exec(&mut *conn)
.await?
.into_iter()
.find(|s| s.name == "free_name" && !s.qualified_name.contains("S::"))
.unwrap();
let refs: Vec<_> = SymbolReference::filter_by_project_id(project)
.exec(&mut *conn)
.await?
.into_iter()
.filter(|r| r.role == "value")
.collect();
assert_eq!(refs.len(), 1, "{refs:?}");
assert_eq!(refs[0].symbol_id, free.id);
Ok(())
}
#[tokio::test]
async fn a_function_in_another_file_can_be_a_value() -> anyhow::Result<()> {
let (db, project) = index_files(
&[
("a.py", "def take(f):\n return f\n\n\ndef go():\n return take(target)\n"),
("z.py", "def target():\n return 1\n\n\nclass K:\n def method(self):\n return 2\n"),
("b.py", "def go2():\n return [method]\n"),
],
IndexOptions::default(),
)
.await?;
assert_eq!(value_lines(&db, project, "target").await?, vec![6]);
assert!(value_lines(&db, project, "method").await?.is_empty());
Ok(())
}