@@ -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