Skip to content

Commit d9957f9

Browse files
[ty] Don't allow inlay hint edits when introducing a non global scope symbol (#24797)
1 parent 043a858 commit d9957f9

1 file changed

Lines changed: 317 additions & 26 deletions

File tree

crates/ty_ide/src/inlay_hints.rs

Lines changed: 317 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ impl InlayHint {
3030
expr: &Expr,
3131
rhs: &Expr,
3232
ty: Type,
33-
allow_edits: bool,
33+
mut allow_edits: bool,
3434
) -> Option<Self> {
3535
let InlayHintImportContext {
3636
db,
@@ -77,10 +77,17 @@ impl InlayHint {
7777
}
7878

7979
// Possibly import the current type and return the qualified name
80-
let qualified_name = |dynamic_importer: &mut DynamicImporter| {
80+
let mut qualified_name = |dynamic_importer: &mut DynamicImporter| {
8181
let type_definition = ty.definition(db)?;
8282
let definition = type_definition.definition()?;
8383

84+
// Only module-level names can be imported with `from <module> import <name>`.
85+
// If the definition lives in a class or function body we can't produce a safe edit.
86+
if !definition.file_scope(db).is_global() {
87+
allow_edits = false;
88+
return None;
89+
}
90+
8491
// Don't try to import symbols in scope
8592
if definition.file(db) == file {
8693
return None;
@@ -2791,7 +2798,7 @@ Source with applied edits:
27912798
"#,
27922799
);
27932800

2794-
assert_snapshot!(test.inlay_hints(), @r"
2801+
assert_snapshot!(test.inlay_hints(), @"
27952802
27962803
from enum import Enum
27972804
@@ -2840,20 +2847,6 @@ Source with applied edits:
28402847
8 | x[: Literal[Color.RED]] = Color.RED
28412848
| ^^^
28422849
|
2843-
2844-
---------------------------------------------
2845-
info[inlay-hint-edit]: Inlay hint edits
2846-
--> main.py:1:1
2847-
1 + from typing import Literal
2848-
2 |
2849-
3 | from enum import Enum
2850-
4 |
2851-
--------------------------------------------------------------------------------
2852-
6 | RED = 1
2853-
7 | BLUE = 2
2854-
8 |
2855-
- x = Color.RED
2856-
9 + x: Literal[Color.RED] = Color.RED
28572850
");
28582851
}
28592852

@@ -5506,15 +5499,6 @@ Source with applied edits:
55065499
6 | ab[: property] = F.whatever
55075500
| ^^^^^^^^
55085501
|
5509-
5510-
---------------------------------------------
5511-
info[inlay-hint-edit]: Inlay hint edits
5512-
--> main.py:1:1
5513-
3 | @property
5514-
4 | def whatever(self): ...
5515-
5 |
5516-
- ab = F.whatever
5517-
6 + ab: property = F.whatever
55185502
");
55195503
}
55205504

@@ -7599,6 +7583,313 @@ Source with applied edits:
75997583
");
76007584
}
76017585

7586+
#[test]
7587+
fn test_auto_import_enum_member() {
7588+
let mut test = inlay_hint_test(
7589+
r#"
7590+
from test import Color
7591+
7592+
x = Color.RED
7593+
"#,
7594+
);
7595+
7596+
test.with_extra_file(
7597+
"test.py",
7598+
r#"
7599+
from enum import Enum
7600+
7601+
class Color(Enum):
7602+
RED = 1
7603+
BLUE = 2
7604+
"#,
7605+
);
7606+
7607+
assert_snapshot!(test.inlay_hints(), @"
7608+
7609+
from test import Color
7610+
7611+
x[: Literal[Color.RED]] = Color.RED
7612+
7613+
---------------------------------------------
7614+
info[inlay-hint-location]: Inlay Hint Target
7615+
--> stdlib/typing.pyi:487:1
7616+
|
7617+
487 | Literal: _SpecialForm
7618+
| ^^^^^^^
7619+
|
7620+
info: Source
7621+
--> main2.py:4:5
7622+
|
7623+
4 | x[: Literal[Color.RED]] = Color.RED
7624+
| ^^^^^^^
7625+
|
7626+
7627+
info[inlay-hint-location]: Inlay Hint Target
7628+
--> test.py:4:19
7629+
|
7630+
4 | class Color(Enum):
7631+
| ^^^^^
7632+
|
7633+
info: Source
7634+
--> main2.py:4:13
7635+
|
7636+
4 | x[: Literal[Color.RED]] = Color.RED
7637+
| ^^^^^
7638+
|
7639+
7640+
info[inlay-hint-location]: Inlay Hint Target
7641+
--> test.py:5:17
7642+
|
7643+
5 | RED = 1
7644+
| ^^^
7645+
|
7646+
info: Source
7647+
--> main2.py:4:19
7648+
|
7649+
4 | x[: Literal[Color.RED]] = Color.RED
7650+
| ^^^
7651+
|
7652+
");
7653+
}
7654+
7655+
/// Regression test for astral-sh/ty#3313: applying the inlay hint on `y`
7656+
/// previously added `Inner` to `from module import Outer`, but `Inner` is
7657+
/// a nested class inside `Outer`, not a top-level symbol of `module`.
7658+
#[test]
7659+
fn test_auto_import_nested_class() {
7660+
let mut test = inlay_hint_test(
7661+
r#"
7662+
from module import Outer
7663+
7664+
7665+
def wrap[T](x: T) -> list[T]:
7666+
return [x]
7667+
7668+
y = wrap(Outer.Inner())
7669+
"#,
7670+
);
7671+
7672+
test.with_extra_file(
7673+
"module.py",
7674+
r#"
7675+
class Outer:
7676+
class Inner: ...
7677+
"#,
7678+
);
7679+
7680+
assert_snapshot!(test.inlay_hints(), @"
7681+
7682+
from module import Outer
7683+
7684+
7685+
def wrap[T](x: T) -> list[T]:
7686+
return [x]
7687+
7688+
y[: list[Inner]] = wrap([x=]Outer.Inner())
7689+
7690+
---------------------------------------------
7691+
info[inlay-hint-location]: Inlay Hint Target
7692+
--> stdlib/builtins.pyi:2829:7
7693+
|
7694+
2829 | class list(MutableSequence[_T]):
7695+
| ^^^^
7696+
|
7697+
info: Source
7698+
--> main2.py:8:5
7699+
|
7700+
8 | y[: list[Inner]] = wrap([x=]Outer.Inner())
7701+
| ^^^^
7702+
|
7703+
7704+
info[inlay-hint-location]: Inlay Hint Target
7705+
--> module.py:3:23
7706+
|
7707+
3 | class Inner: ...
7708+
| ^^^^^
7709+
|
7710+
info: Source
7711+
--> main2.py:8:10
7712+
|
7713+
8 | y[: list[Inner]] = wrap([x=]Outer.Inner())
7714+
| ^^^^^
7715+
|
7716+
7717+
info[inlay-hint-location]: Inlay Hint Target
7718+
--> main.py:5:13
7719+
|
7720+
5 | def wrap[T](x: T) -> list[T]:
7721+
| ^
7722+
|
7723+
info: Source
7724+
--> main2.py:8:26
7725+
|
7726+
8 | y[: list[Inner]] = wrap([x=]Outer.Inner())
7727+
| ^
7728+
|
7729+
7730+
---------------------------------------------
7731+
info[inlay-hint-edit]: Inlay hint edits
7732+
--> main.py:1:1
7733+
5 | def wrap[T](x: T) -> list[T]:
7734+
6 | return [x]
7735+
7 |
7736+
- y = wrap(Outer.Inner())
7737+
8 + y = wrap(x=Outer.Inner())
7738+
");
7739+
}
7740+
7741+
#[test]
7742+
fn test_auto_import_enum_member_unimported_class() {
7743+
let mut test = inlay_hint_test(
7744+
r#"
7745+
import test
7746+
7747+
x = test.Color.RED
7748+
"#,
7749+
);
7750+
7751+
test.with_extra_file(
7752+
"test.py",
7753+
r#"
7754+
from enum import Enum
7755+
7756+
class Color(Enum):
7757+
RED = 1
7758+
BLUE = 2
7759+
"#,
7760+
);
7761+
7762+
assert_snapshot!(test.inlay_hints(), @"
7763+
7764+
import test
7765+
7766+
x[: Literal[Color.RED]] = test.Color.RED
7767+
7768+
---------------------------------------------
7769+
info[inlay-hint-location]: Inlay Hint Target
7770+
--> stdlib/typing.pyi:487:1
7771+
|
7772+
487 | Literal: _SpecialForm
7773+
| ^^^^^^^
7774+
|
7775+
info: Source
7776+
--> main2.py:4:5
7777+
|
7778+
4 | x[: Literal[Color.RED]] = test.Color.RED
7779+
| ^^^^^^^
7780+
|
7781+
7782+
info[inlay-hint-location]: Inlay Hint Target
7783+
--> test.py:4:19
7784+
|
7785+
4 | class Color(Enum):
7786+
| ^^^^^
7787+
|
7788+
info: Source
7789+
--> main2.py:4:13
7790+
|
7791+
4 | x[: Literal[Color.RED]] = test.Color.RED
7792+
| ^^^^^
7793+
|
7794+
7795+
info[inlay-hint-location]: Inlay Hint Target
7796+
--> test.py:5:17
7797+
|
7798+
5 | RED = 1
7799+
| ^^^
7800+
|
7801+
info: Source
7802+
--> main2.py:4:19
7803+
|
7804+
4 | x[: Literal[Color.RED]] = test.Color.RED
7805+
| ^^^
7806+
|
7807+
");
7808+
}
7809+
7810+
#[test]
7811+
fn test_auto_import_method_returning_nested_class() {
7812+
let mut test = inlay_hint_test(
7813+
r#"
7814+
from module import Outer
7815+
7816+
x = Outer().make()
7817+
"#,
7818+
);
7819+
7820+
test.with_extra_file(
7821+
"module.py",
7822+
r#"
7823+
class Outer:
7824+
class Inner: ...
7825+
7826+
def make(self) -> "Outer.Inner":
7827+
return Outer.Inner()
7828+
"#,
7829+
);
7830+
7831+
assert_snapshot!(test.inlay_hints(), @"
7832+
7833+
from module import Outer
7834+
7835+
x[: Inner] = Outer().make()
7836+
7837+
---------------------------------------------
7838+
info[inlay-hint-location]: Inlay Hint Target
7839+
--> module.py:3:23
7840+
|
7841+
3 | class Inner: ...
7842+
| ^^^^^
7843+
|
7844+
info: Source
7845+
--> main2.py:4:5
7846+
|
7847+
4 | x[: Inner] = Outer().make()
7848+
| ^^^^^
7849+
|
7850+
");
7851+
}
7852+
7853+
#[test]
7854+
fn test_auto_import_same_file_method_returning_nested_class() {
7855+
let mut test = inlay_hint_test(
7856+
r#"
7857+
class Outer:
7858+
class Inner: ...
7859+
7860+
def make(self) -> "Outer.Inner":
7861+
return Outer.Inner()
7862+
7863+
x = Outer().make()
7864+
"#,
7865+
);
7866+
7867+
assert_snapshot!(test.inlay_hints(), @r#"
7868+
7869+
class Outer:
7870+
class Inner: ...
7871+
7872+
def make(self) -> "Outer.Inner":
7873+
return Outer.Inner()
7874+
7875+
x[: Inner] = Outer().make()
7876+
7877+
---------------------------------------------
7878+
info[inlay-hint-location]: Inlay Hint Target
7879+
--> main.py:3:11
7880+
|
7881+
3 | class Inner: ...
7882+
| ^^^^^
7883+
|
7884+
info: Source
7885+
--> main2.py:8:5
7886+
|
7887+
8 | x[: Inner] = Outer().make()
7888+
| ^^^^^
7889+
|
7890+
"#);
7891+
}
7892+
76027893
struct InlayHintLocationDiagnostic {
76037894
source: FileRange,
76047895
target: FileRange,

0 commit comments

Comments
 (0)