mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-08 08:52:12 +00:00
446 lines
16 KiB
Rust
446 lines
16 KiB
Rust
//! `locals` per language: shadowing, nested functions, lambdas, member
|
|
//! positions and broken code (a missing `;`).
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn java() {
|
|
let src = r#"class A {
|
|
int x;
|
|
int f(int a) {
|
|
int x = 1;
|
|
{ int y = x + a; }
|
|
Runnable r = () -> foo(x);
|
|
Function<Integer, Integer> g = (b) -> b + this.x + a;
|
|
String s = "x" + obj.x + obj.x() + x;
|
|
for (int i = 0; i < x; i++) { int a2 = i; }
|
|
try { run(); } catch (Exception e) { log(e, x); }
|
|
return x;
|
|
}
|
|
void h() { x(); y = x; }
|
|
}
|
|
"#;
|
|
// `x` #2 is the local; #1 the field; #5 `this.x`, #6 `"x"`, #7/#8 members.
|
|
assert_local("java", src, "x", 2, 2, &[3, 4, 9, 10, 11, 12]);
|
|
assert_local("java", src, "x", 12, 2, &[3, 4, 9, 10, 11, 12]);
|
|
assert_eq!(assert_not_local("java", src, "x", 5), NotLocalReason::Member);
|
|
assert_eq!(assert_not_local("java", src, "x", 7), NotLocalReason::Member);
|
|
assert_eq!(assert_not_local("java", src, "x", 8), NotLocalReason::Member);
|
|
assert_eq!(assert_not_local("java", src, "x", 13), NotLocalReason::Member, "method name");
|
|
assert_eq!(assert_not_local("java", src, "x", 1), NotLocalReason::Member, "field");
|
|
assert_eq!(assert_not_local("java", src, "x", 14), NotLocalReason::Unresolved, "field read outside `f`");
|
|
assert_local("java", src, "a", 2, 1, &[2, 3]);
|
|
assert_local("java", src, "b", 2, 1, &[2]);
|
|
assert_local("java", src, "i", 2, 1, &[2, 3, 4]);
|
|
assert_local("java", src, "e", 1, 1, &[2]);
|
|
assert!(matches!(locals_at("java", src, "f", 1), Locals::NotLocal { .. }));
|
|
assert!(matches!(locals_at("java", "class A { }", "class", 1), Locals::NoIdentifier));
|
|
|
|
// An anonymous class's field shadows the local inside the class only.
|
|
let anon = "class A {\n void f() {\n int n = 1;\n Object o = new Object() {\n int n = 2;\n int g() { return n; }\n };\n use(n);\n }\n}\n";
|
|
assert_local("java", anon, "n", 1, 1, &[4]);
|
|
assert_eq!(assert_not_local("java", anon, "n", 3), NotLocalReason::Member);
|
|
|
|
// A missing `;` must not break resolution of the surrounding locals.
|
|
let broken = "class A {\n void f() {\n int x = 1\n int y = x + 2;\n use(x, y);\n }\n}\n";
|
|
assert_local("java", broken, "x", 3, 1, &[2, 3]);
|
|
assert_local("java", broken, "y", 2, 1, &[2]);
|
|
}
|
|
|
|
#[test]
|
|
fn kotlin() {
|
|
let src = r#"class A(val x: Int) {
|
|
fun f(a: Int): Int {
|
|
val x = 1
|
|
if (a > 0) { val y = x + a }
|
|
val g = { b: Int -> b + x }
|
|
val h = listOf(1).map { it + x }
|
|
val s = obj.x + f(a = x)
|
|
fun inner(c: Int) = c + x
|
|
return x
|
|
}
|
|
}
|
|
"#;
|
|
assert_local("kotlin", src, "x", 2, 2, &[3, 4, 5, 7, 8, 9]);
|
|
assert_eq!(assert_not_local("kotlin", src, "x", 6), NotLocalReason::Member);
|
|
assert_eq!(assert_not_local("kotlin", src, "x", 1), NotLocalReason::Member, "property");
|
|
assert_eq!(assert_not_local("kotlin", src, "a", 4), NotLocalReason::Member, "named argument label");
|
|
assert_local("kotlin", src, "a", 1, 1, &[2, 3]);
|
|
assert_local("kotlin", src, "b", 1, 1, &[2]);
|
|
assert_local("kotlin", src, "c", 2, 1, &[2]);
|
|
assert_local("kotlin", src, "inner", 1, 1, &[]);
|
|
assert!(matches!(locals_at("kotlin", src, "x", 2), Locals::Local { symbol } if symbol.certain));
|
|
// tree-sitter-kotlin-ng keeps `"$x"` as plain string text: the rename would miss it.
|
|
let tpl = "fun f() {\n val x = 1\n println(\"$x\")\n}\n";
|
|
assert!(matches!(locals_at("kotlin", tpl, "x", 1), Locals::Local { symbol } if !symbol.certain));
|
|
}
|
|
|
|
#[test]
|
|
fn python() {
|
|
let src = r#"class A:
|
|
x = 1
|
|
def f(self, a, *args, k=2):
|
|
x = 1
|
|
def inner(y):
|
|
x = y
|
|
return x
|
|
g = lambda b: b + x
|
|
s = f"x {x}" + obj.x + call(x=x)
|
|
x = x + 1
|
|
for i in range(x):
|
|
print(i, a, k, args)
|
|
return [x for x in range(3)]
|
|
"#;
|
|
// `x` #2 is the local of `f`; inner's `x` (#3, #4) shadows it; the comprehension (#14, #15) too.
|
|
assert_local("python", src, "x", 2, 2, &[5, 7, 10, 11, 12, 13]);
|
|
assert_local("python", src, "x", 12, 2, &[5, 7, 10, 11, 12, 13]);
|
|
assert_local("python", src, "x", 4, 3, &[4]);
|
|
assert_local("python", src, "x", 14, 15, &[14]);
|
|
assert_eq!(assert_not_local("python", src, "x", 8), NotLocalReason::Member);
|
|
assert_eq!(assert_not_local("python", src, "x", 9), NotLocalReason::Member, "keyword argument");
|
|
assert_eq!(assert_not_local("python", src, "x", 1), NotLocalReason::TopLevel, "class attribute");
|
|
assert_local("python", src, "args", 1, 1, &[2]);
|
|
assert_local("python", src, "k", 1, 1, &[2]);
|
|
assert_local("python", src, "inner", 1, 1, &[]);
|
|
assert!(matches!(
|
|
locals_at("python", src, "print", 1),
|
|
Locals::NotLocal { reason: NotLocalReason::Unresolved, .. }
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn javascript_and_typescript() {
|
|
let src = r#"function f(a, {b, c: d}, ...rest) {
|
|
let x = 1;
|
|
{ const x = 2; use(x); }
|
|
const g = (y) => y + x;
|
|
const h = z => z;
|
|
const s = `x ${x}` + obj.x + {x}.x;
|
|
class K { m(p) { return p + x + this.x; } }
|
|
function inner() { return x + a + b + d + rest; }
|
|
let w = x
|
|
return x;
|
|
}
|
|
"#;
|
|
for lang in ["javascript", "typescript", "typescriptreact"] {
|
|
assert_local(lang, src, "x", 1, 1, &[4, 6, 8, 10, 12, 13, 14]);
|
|
assert_local(lang, src, "x", 3, 2, &[3]);
|
|
assert_eq!(assert_not_local(lang, src, "x", 7), NotLocalReason::Member);
|
|
assert_eq!(assert_not_local(lang, src, "x", 9), NotLocalReason::Member);
|
|
assert_eq!(assert_not_local(lang, src, "x", 11), NotLocalReason::Member);
|
|
assert_local(lang, src, "a", 2, 1, &[2]);
|
|
assert_local(lang, src, "b", 1, 1, &[2]);
|
|
assert_local(lang, src, "d", 2, 1, &[2]);
|
|
assert_local(lang, src, "rest", 1, 1, &[2]);
|
|
assert_local(lang, src, "y", 1, 1, &[2]);
|
|
assert_local(lang, src, "z", 2, 1, &[2]);
|
|
assert_local(lang, src, "p", 1, 1, &[2]);
|
|
assert_local(lang, src, "inner", 1, 1, &[]);
|
|
assert_eq!(assert_not_local(lang, src, "f", 1), NotLocalReason::TopLevel);
|
|
}
|
|
// Class members never shadow locals in JS: they are only reachable through `this`.
|
|
let cls = "function f() {\n let m = 1;\n class K { m = 2; m2() { return m; } }\n return m;\n}\n";
|
|
assert_local("javascript", cls, "m", 1, 1, &[3, 4]);
|
|
let ts = "function t<T>(a: T, b?: number): T {\n const x: number = b ?? 1;\n return a ?? x;\n}\n";
|
|
assert_local("typescript", ts, "a", 1, 1, &[2]);
|
|
assert_local("typescript", ts, "b", 2, 1, &[2]);
|
|
assert_local("typescript", ts, "x", 2, 1, &[2]);
|
|
}
|
|
|
|
#[test]
|
|
fn rust() {
|
|
let src = r#"fn f(a: u32, (p, q): (u32, u32)) -> u32 {
|
|
let x = 1;
|
|
let x = x + a;
|
|
{ let y = x; }
|
|
let g = |b: u32| b + x;
|
|
let s = S { x, y: x };
|
|
let t = obj.x + obj.x();
|
|
match a { Some(v) => v + x, _ => 0 }
|
|
println!("{}", x);
|
|
let z = x
|
|
x + p + q
|
|
}
|
|
"#;
|
|
// The second `let x` shadows the first from the next statement on.
|
|
assert_local("rust", src, "x", 1, 1, &[3]);
|
|
assert_local("rust", src, "x", 3, 1, &[3]);
|
|
assert_local("rust", src, "x", 2, 2, &[4, 5, 6, 7, 10, 11, 12, 13]);
|
|
assert_local("rust", src, "x", 13, 2, &[4, 5, 6, 7, 10, 11, 12, 13]);
|
|
assert_eq!(assert_not_local("rust", src, "x", 8), NotLocalReason::Member);
|
|
assert_eq!(assert_not_local("rust", src, "x", 9), NotLocalReason::Member);
|
|
assert_local("rust", src, "v", 1, 1, &[2]);
|
|
assert_local("rust", src, "b", 2, 1, &[2]);
|
|
assert_local("rust", src, "p", 2, 1, &[2]);
|
|
assert_local("rust", src, "a", 1, 1, &[2, 3]);
|
|
match locals_at("rust", src, "x", 2) {
|
|
Locals::Local { symbol } => {
|
|
assert!(symbol.certain);
|
|
assert_eq!(symbol.kind, LocalKind::Var);
|
|
}
|
|
other => panic!("{other:?}"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn go() {
|
|
let src = r#"package main
|
|
func f(a int) int {
|
|
x := 1
|
|
if y := x + a; y > 0 { x := 2; use(x) }
|
|
g := func(b int) int { return b + x }
|
|
s := obj.x + P{x: x}.x
|
|
for i, v := range xs { use(i, v) }
|
|
var z int = x
|
|
return x
|
|
}
|
|
"#;
|
|
assert_local("go", src, "x", 1, 1, &[2, 5, 8, 10, 11]);
|
|
assert_local("go", src, "x", 4, 3, &[4]);
|
|
assert_eq!(assert_not_local("go", src, "x", 6), NotLocalReason::Member);
|
|
assert_eq!(assert_not_local("go", src, "x", 7), NotLocalReason::Member, "composite literal key");
|
|
assert_local("go", src, "y", 1, 1, &[2]);
|
|
assert_local("go", src, "b", 2, 1, &[2]);
|
|
assert_local("go", src, "v", 1, 1, &[2]);
|
|
assert_eq!(assert_not_local("go", src, "f", 1), NotLocalReason::TopLevel);
|
|
}
|
|
|
|
#[test]
|
|
fn c_and_cpp() {
|
|
let src = r#"int f(int a) {
|
|
int x = 1;
|
|
{ int x = 2; use(x); }
|
|
for (int i = 0; i < x; i++) { use(i); }
|
|
int s = obj.x + p->x;
|
|
int z = x
|
|
return x + a;
|
|
}
|
|
"#;
|
|
for lang in ["c", "cpp"] {
|
|
assert_local(lang, src, "x", 1, 1, &[4, 7, 8]);
|
|
assert_local(lang, src, "x", 3, 2, &[3]);
|
|
assert_eq!(assert_not_local(lang, src, "x", 5), NotLocalReason::Member);
|
|
assert_eq!(assert_not_local(lang, src, "x", 6), NotLocalReason::Member);
|
|
assert_local(lang, src, "i", 1, 1, &[2, 3, 4]);
|
|
assert_local(lang, src, "a", 1, 1, &[2]);
|
|
}
|
|
let cpp = "int f(int a) {\n int x = 1;\n auto g = [x](int b) { return b + x; };\n for (auto& v : xs) { use(v); }\n return ns::x + x;\n}\n";
|
|
assert_local("cpp", cpp, "b", 1, 1, &[2]);
|
|
assert_local("cpp", cpp, "v", 1, 1, &[2]);
|
|
assert_eq!(assert_not_local("cpp", cpp, "x", 4), NotLocalReason::Member);
|
|
}
|
|
|
|
#[test]
|
|
fn csharp() {
|
|
let src = r#"class A {
|
|
int x;
|
|
int F(int a) {
|
|
var x = 1;
|
|
{ var y = x + a; }
|
|
Func<int, int> g = b => b + x;
|
|
foreach (var v in xs) { Use(v); }
|
|
var s = obj.x + this.x + Call(x: x);
|
|
var z = x
|
|
return x;
|
|
}
|
|
}
|
|
"#;
|
|
// `var z = x` lacks its `;`: `return x` ends up inside an ERROR node.
|
|
assert_local("csharp", src, "x", 2, 2, &[3, 4, 8, 9]);
|
|
assert!(matches!(locals_at("csharp", src, "x", 2), Locals::Local { symbol } if !symbol.certain));
|
|
assert_eq!(assert_not_local("csharp", src, "x", 5), NotLocalReason::Member);
|
|
assert_eq!(assert_not_local("csharp", src, "x", 6), NotLocalReason::Member);
|
|
assert_eq!(assert_not_local("csharp", src, "x", 7), NotLocalReason::Member, "named argument");
|
|
assert_local("csharp", src, "b", 1, 1, &[2]);
|
|
assert_local("csharp", src, "v", 1, 1, &[2]);
|
|
assert_local("csharp", src, "a", 1, 1, &[2]);
|
|
}
|
|
|
|
#[test]
|
|
fn ruby() {
|
|
let src = r#"class A
|
|
def f(a, *rest, k: 1, &blk)
|
|
x = 1
|
|
[1].each { |y| puts y + x }
|
|
g = ->(b) { b + x }
|
|
s = "x #{x}" + obj.x + obj.x(1)
|
|
x = x + a
|
|
x
|
|
end
|
|
end
|
|
"#;
|
|
// Assignment declares: both `x =` lines are the same variable.
|
|
assert_local("ruby", src, "x", 1, 1, &[2, 3, 5, 8, 9, 10]);
|
|
assert_eq!(assert_not_local("ruby", src, "x", 6), NotLocalReason::Member);
|
|
assert_local("ruby", src, "y", 1, 1, &[2]);
|
|
assert_local("ruby", src, "b", 1, 1, &[2]);
|
|
assert_local("ruby", src, "a", 1, 1, &[2]);
|
|
}
|
|
|
|
#[test]
|
|
fn php() {
|
|
let src = r#"<?php
|
|
function f($a) {
|
|
$x = 1;
|
|
$g = function($b) use ($x) { return $b + $x; };
|
|
$h = fn($c) => $c + $x;
|
|
foreach ($xs as $k => $v) { echo $v; }
|
|
$s = "x $x" . $obj->x . $obj->x();
|
|
$x = $x + $a;
|
|
return $x;
|
|
}
|
|
"#;
|
|
// Names are the `name` inside `$x`; the `use ($x)` clause declares a closure-local copy.
|
|
assert_local("php", src, "x", 1, 1, &[4, 6, 9, 10, 11]);
|
|
assert_local("php", src, "x", 3, 2, &[3]);
|
|
assert_eq!(assert_not_local("php", src, "x", 7), NotLocalReason::Member);
|
|
assert_eq!(assert_not_local("php", src, "x", 8), NotLocalReason::Member);
|
|
assert_local("php", src, "b", 2, 1, &[2]);
|
|
assert_local("php", src, "c", 2, 1, &[2]);
|
|
assert_local("php", src, "v", 1, 1, &[2]);
|
|
}
|
|
|
|
#[test]
|
|
fn swift() {
|
|
let src = r#"func f(a: Int, _ b: Int) -> Int {
|
|
let x = 1
|
|
if a > 0 { let x = 2; use(x) }
|
|
let g = { (c: Int) -> Int in c + x }
|
|
let s = "x \(x)" + obj.x
|
|
for v in xs { use(v) }
|
|
return x + b
|
|
}
|
|
"#;
|
|
assert_local("swift", src, "x", 1, 1, &[4, 6, 8]);
|
|
assert_local("swift", src, "x", 3, 2, &[3]);
|
|
assert_eq!(assert_not_local("swift", src, "x", 7), NotLocalReason::Member);
|
|
assert_local("swift", src, "v", 1, 1, &[2]);
|
|
assert_local("swift", src, "a", 1, 1, &[2]);
|
|
}
|
|
|
|
#[test]
|
|
fn scala() {
|
|
let src = r#"object O {
|
|
def f(a: Int): Int = {
|
|
val x = 1
|
|
if (a > 0) { val x = 2; use(x) }
|
|
val g = (b: Int) => b + x
|
|
val s = obj.x
|
|
for (v <- xs) use(v)
|
|
x
|
|
}
|
|
}
|
|
"#;
|
|
assert_local("scala", src, "x", 1, 1, &[4, 6]);
|
|
assert_local("scala", src, "x", 3, 2, &[3]);
|
|
assert_eq!(assert_not_local("scala", src, "x", 5), NotLocalReason::Member);
|
|
assert_local("scala", src, "b", 1, 1, &[2]);
|
|
assert_local("scala", src, "a", 1, 1, &[2]);
|
|
}
|
|
|
|
#[test]
|
|
fn lua() {
|
|
let src = r#"local function f(a, ...)
|
|
local x = 1
|
|
do local x = 2; use(x) end
|
|
local g = function(b) return b + x end
|
|
local s = obj.x .. obj:x() .. "x"
|
|
for i, v in ipairs(t) do use(i, v) end
|
|
local x = x + a
|
|
return x
|
|
end
|
|
"#;
|
|
// `local x = x + a` reads the outer `x` (#9) and declares a new one (#8).
|
|
assert_local("lua", src, "x", 1, 1, &[4, 9]);
|
|
assert_local("lua", src, "x", 3, 2, &[3]);
|
|
assert_local("lua", src, "x", 10, 8, &[10]);
|
|
assert_eq!(assert_not_local("lua", src, "x", 5), NotLocalReason::Member);
|
|
assert_eq!(assert_not_local("lua", src, "x", 6), NotLocalReason::Member);
|
|
assert_local("lua", src, "b", 1, 1, &[2]);
|
|
assert_local("lua", src, "v", 1, 1, &[2]);
|
|
}
|
|
|
|
#[test]
|
|
fn bash() {
|
|
let src = "f() {\n local x=1\n y=\"$x and ${x}\"\n echo $x $y\n for i in 1 2; do echo $i; done\n}\n";
|
|
assert_local("shellscript", src, "x", 1, 1, &[2, 3, 4]);
|
|
// Loop variables and plain assignments are global in Bash.
|
|
assert_eq!(assert_not_local("shellscript", src, "i", 2), NotLocalReason::Unresolved);
|
|
assert_eq!(assert_not_local("shellscript", src, "y", 2), NotLocalReason::Unresolved);
|
|
}
|
|
|
|
#[test]
|
|
fn dart() {
|
|
let src = r#"class A {
|
|
int x = 0;
|
|
int f(int a, {int k = 1}) {
|
|
var x = 1;
|
|
if (a > 0) { var x = 2; use(x); }
|
|
var g = (int b) => b + x;
|
|
var s = obj.x;
|
|
for (var v in xs) { use(v); }
|
|
var z = x
|
|
return x + k;
|
|
}
|
|
}
|
|
"#;
|
|
// `var z = x` lacks its `;`: the parser swallows `return x + k` into an ERROR token.
|
|
assert_local("dart", src, "x", 2, 2, &[5, 7]);
|
|
assert!(matches!(locals_at("dart", src, "x", 2), Locals::Local { symbol } if !symbol.certain));
|
|
assert_local("dart", src, "x", 4, 3, &[4]);
|
|
assert_eq!(assert_not_local("dart", src, "x", 6), NotLocalReason::Member);
|
|
assert_eq!(assert_not_local("dart", src, "x", 1), NotLocalReason::Member, "field");
|
|
assert_local("dart", src, "b", 1, 1, &[2]);
|
|
assert_local("dart", src, "v", 1, 1, &[2]);
|
|
assert_local("dart", src, "k", 1, 1, &[2]);
|
|
}
|
|
|
|
#[test]
|
|
fn elixir() {
|
|
let src = r#"defmodule M do
|
|
def f(a, %{k: v}) do
|
|
x = 1
|
|
g = fn b -> b + x end
|
|
s = obj.x
|
|
Enum.map(xs, fn y -> y + x end)
|
|
x + a + v
|
|
end
|
|
end
|
|
"#;
|
|
assert_local("elixir", src, "x", 1, 1, &[2, 4, 5]);
|
|
assert_eq!(assert_not_local("elixir", src, "x", 3), NotLocalReason::Member);
|
|
assert_local("elixir", src, "b", 1, 1, &[2]);
|
|
assert_local("elixir", src, "y", 1, 1, &[2]);
|
|
assert_local("elixir", src, "a", 1, 1, &[2]);
|
|
}
|
|
|
|
#[test]
|
|
fn zig() {
|
|
let src = r#"fn f(a: u32) u32 {
|
|
const x: u32 = 1;
|
|
{
|
|
const y = x + a;
|
|
_ = y;
|
|
}
|
|
for (xs) |v| { use(v); }
|
|
const s = obj.x + x;
|
|
var z = x
|
|
return x;
|
|
}
|
|
"#;
|
|
assert_local("zig", src, "x", 1, 1, &[2, 4, 5, 6]);
|
|
assert_eq!(assert_not_local("zig", src, "x", 3), NotLocalReason::Member);
|
|
assert_local("zig", src, "y", 1, 1, &[2]);
|
|
assert_local("zig", src, "v", 1, 1, &[2]);
|
|
assert_local("zig", src, "a", 1, 1, &[2]);
|
|
}
|
|
|
|
#[test]
|
|
fn haskell() {
|
|
let src =
|
|
"module M where\nf :: Int -> Int\nf a = let x = 1 in x + a + g a\n where g b = b + 1\nh c = \\d -> c + d\n";
|
|
assert_local("haskell", src, "x", 1, 1, &[2]);
|
|
assert_local("haskell", src, "a", 1, 1, &[2, 3]);
|
|
assert_local("haskell", src, "d", 1, 1, &[2]);
|
|
}
|