From a561e6659d52f3a56566f1b71b613dbd5a871809 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Mon, 1 Dec 2025 09:44:31 -0500 Subject: [PATCH] [ty] Exclude `typing_extensions` from completions unless it's really available This works by adding a third module resolution mode that lets the caller opt into _some_ shadowing of modules that is otherwise not allowed (for `typing` and `typing_extensions`). Fixes astral-sh/ty#1658 --- .../completion-evaluation-tasks.csv | 2 +- crates/ty_ide/src/all_symbols.rs | 19 +++- crates/ty_ide/src/completion.rs | 87 ++++++++++++++++-- crates/ty_python_semantic/src/lib.rs | 3 +- .../src/module_resolver/list.rs | 7 +- .../src/module_resolver/mod.rs | 2 +- .../src/module_resolver/resolver.rs | 89 +++++++++++++++---- .../ty_python_semantic/src/semantic_model.rs | 8 +- ...ions__code_action_undefined_decorator.snap | 46 ---------- ...code_action_undefined_reference_multi.snap | 46 ---------- .../snapshots/e2e__notebook__auto_import.snap | 44 +-------- .../e2e__notebook__auto_import_docstring.snap | 44 +-------- ...2e__notebook__auto_import_from_future.snap | 44 +-------- .../e2e__notebook__auto_import_same_cell.snap | 44 +-------- 14 files changed, 188 insertions(+), 297 deletions(-) diff --git a/crates/ty_completion_eval/completion-evaluation-tasks.csv b/crates/ty_completion_eval/completion-evaluation-tasks.csv index 8e62771cd9..f8347cb8e5 100644 --- a/crates/ty_completion_eval/completion-evaluation-tasks.csv +++ b/crates/ty_completion_eval/completion-evaluation-tasks.csv @@ -25,4 +25,4 @@ scope-simple-long-identifier,main.py,0,1 tstring-completions,main.py,0,1 ty-extensions-lower-stdlib,main.py,0,8 type-var-typing-over-ast,main.py,0,3 -type-var-typing-over-ast,main.py,1,278 +type-var-typing-over-ast,main.py,1,275 diff --git a/crates/ty_ide/src/all_symbols.rs b/crates/ty_ide/src/all_symbols.rs index 46fff1593b..5f5774cd69 100644 --- a/crates/ty_ide/src/all_symbols.rs +++ b/crates/ty_ide/src/all_symbols.rs @@ -1,6 +1,6 @@ use ruff_db::files::File; use ty_project::Db; -use ty_python_semantic::{Module, all_modules}; +use ty_python_semantic::{Module, ModuleName, all_modules, resolve_real_shadowable_module}; use crate::symbols::{QueryPattern, SymbolInfo, symbols_for_file_global_only}; @@ -8,12 +8,20 @@ use crate::symbols::{QueryPattern, SymbolInfo, symbols_for_file_global_only}; /// /// Returns symbols from all files in the workspace and dependencies, filtered /// by the query. -pub fn all_symbols<'db>(db: &'db dyn Db, query: &QueryPattern) -> Vec> { +pub fn all_symbols<'db>( + db: &'db dyn Db, + importing_from: File, + query: &QueryPattern, +) -> Vec> { // If the query is empty, return immediately to avoid expensive file scanning if query.will_match_everything() { return Vec::new(); } + let typing_extensions = ModuleName::new("typing_extensions").unwrap(); + let is_typing_extensions_available = importing_from.is_stub(db) + || resolve_real_shadowable_module(db, &typing_extensions).is_some(); + let results = std::sync::Mutex::new(Vec::new()); { let modules = all_modules(db); @@ -28,6 +36,11 @@ pub fn all_symbols<'db>(db: &'db dyn Db, query: &QueryPattern) -> Vec String { - let symbols = all_symbols(&self.db, &QueryPattern::fuzzy(query)); + let symbols = all_symbols(&self.db, self.cursor.file, &QueryPattern::fuzzy(query)); if symbols.is_empty() { return "No symbols found".to_string(); diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index 8b4c38beb4..45576ae531 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -517,7 +517,7 @@ fn add_unimported_completions<'db>( let importer = Importer::new(db, &stylist, file, source.as_str(), parsed); let members = importer.members_in_scope_at(scoped.node, scoped.node.start()); - for symbol in all_symbols(db, &completions.query) { + for symbol in all_symbols(db, file, &completions.query) { if symbol.module.file(db) == Some(file) || symbol.module.is_known(db, KnownModule::Builtins) { continue; @@ -5566,10 +5566,7 @@ def foo(param: s) #[test] fn from_import_no_space_not_suggests_import() { let builder = completion_test_builder("from typing"); - assert_snapshot!(builder.build().snapshot(), @r" - typing - typing_extensions - "); + assert_snapshot!(builder.build().snapshot(), @"typing"); } #[test] @@ -5785,6 +5782,86 @@ from .imp "); } + #[test] + fn typing_extensions_excluded_from_import() { + let builder = completion_test_builder("from typing").module_names(); + assert_snapshot!(builder.build().snapshot(), @"typing :: Current module"); + } + + #[test] + fn typing_extensions_excluded_from_auto_import() { + let builder = completion_test_builder("deprecated") + .auto_import() + .module_names(); + assert_snapshot!(builder.build().snapshot(), @r" + Deprecated :: importlib.metadata + DeprecatedList :: importlib.metadata + DeprecatedNonAbstract :: importlib.metadata + DeprecatedTuple :: importlib.metadata + deprecated :: warnings + "); + } + + #[test] + fn typing_extensions_included_from_import() { + let builder = CursorTest::builder() + .source("typing_extensions.py", "deprecated = 1") + .source("foo.py", "from typing") + .completion_test_builder() + .module_names(); + assert_snapshot!(builder.build().snapshot(), @r" + typing :: Current module + typing_extensions :: Current module + "); + } + + #[test] + fn typing_extensions_included_from_auto_import() { + let builder = CursorTest::builder() + .source("typing_extensions.py", "deprecated = 1") + .source("foo.py", "deprecated") + .completion_test_builder() + .auto_import() + .module_names(); + assert_snapshot!(builder.build().snapshot(), @r" + Deprecated :: importlib.metadata + DeprecatedList :: importlib.metadata + DeprecatedNonAbstract :: importlib.metadata + DeprecatedTuple :: importlib.metadata + deprecated :: typing_extensions + deprecated :: warnings + "); + } + + #[test] + fn typing_extensions_included_from_import_in_stub() { + let builder = CursorTest::builder() + .source("foo.pyi", "from typing") + .completion_test_builder() + .module_names(); + assert_snapshot!(builder.build().snapshot(), @r" + typing :: Current module + typing_extensions :: Current module + "); + } + + #[test] + fn typing_extensions_included_from_auto_import_in_stub() { + let builder = CursorTest::builder() + .source("foo.pyi", "deprecated") + .completion_test_builder() + .auto_import() + .module_names(); + assert_snapshot!(builder.build().snapshot(), @r" + Deprecated :: importlib.metadata + DeprecatedList :: importlib.metadata + DeprecatedNonAbstract :: importlib.metadata + DeprecatedTuple :: importlib.metadata + deprecated :: typing_extensions + deprecated :: warnings + "); + } + /// A way to create a simple single-file (named `main.py`) completion test /// builder. /// diff --git a/crates/ty_python_semantic/src/lib.rs b/crates/ty_python_semantic/src/lib.rs index be50dc9b52..8c1776e154 100644 --- a/crates/ty_python_semantic/src/lib.rs +++ b/crates/ty_python_semantic/src/lib.rs @@ -13,7 +13,8 @@ pub use diagnostic::add_inferred_python_version_hint_to_diagnostic; pub use module_name::{ModuleName, ModuleNameResolutionError}; pub use module_resolver::{ KnownModule, Module, SearchPath, SearchPathValidationError, SearchPaths, all_modules, - list_modules, resolve_module, resolve_real_module, system_module_search_paths, + list_modules, resolve_module, resolve_real_module, resolve_real_shadowable_module, + system_module_search_paths, }; pub use program::{ Program, ProgramSettings, PythonVersionFileSource, PythonVersionSource, diff --git a/crates/ty_python_semantic/src/module_resolver/list.rs b/crates/ty_python_semantic/src/module_resolver/list.rs index a7957e3c98..1f87ae5a34 100644 --- a/crates/ty_python_semantic/src/module_resolver/list.rs +++ b/crates/ty_python_semantic/src/module_resolver/list.rs @@ -8,9 +8,7 @@ use crate::program::Program; use super::module::{Module, ModuleKind}; use super::path::{ModulePath, SearchPath, SystemOrVendoredPathRef}; -use super::resolver::{ - ModuleResolveMode, ResolverContext, is_non_shadowable, resolve_file_module, search_paths, -}; +use super::resolver::{ModuleResolveMode, ResolverContext, resolve_file_module, search_paths}; /// List all available modules, including all sub-modules, sorted in lexicographic order. pub fn all_modules(db: &dyn Db) -> Vec> { @@ -309,7 +307,8 @@ impl<'db> Lister<'db> { /// Returns true if the given module name cannot be shadowable. fn is_non_shadowable(&self, name: &ModuleName) -> bool { - is_non_shadowable(self.python_version().minor, name.as_str()) + ModuleResolveMode::StubsAllowed + .is_non_shadowable(self.python_version().minor, name.as_str()) } /// Returns the Python version we want to perform module resolution diff --git a/crates/ty_python_semantic/src/module_resolver/mod.rs b/crates/ty_python_semantic/src/module_resolver/mod.rs index 11d03cf7b5..cc541d9b31 100644 --- a/crates/ty_python_semantic/src/module_resolver/mod.rs +++ b/crates/ty_python_semantic/src/module_resolver/mod.rs @@ -6,7 +6,7 @@ pub use module::Module; pub use path::{SearchPath, SearchPathValidationError}; pub use resolver::SearchPaths; pub(crate) use resolver::file_to_module; -pub use resolver::{resolve_module, resolve_real_module}; +pub use resolver::{resolve_module, resolve_real_module, resolve_real_shadowable_module}; use ruff_db::system::SystemPath; use crate::Db; diff --git a/crates/ty_python_semantic/src/module_resolver/resolver.rs b/crates/ty_python_semantic/src/module_resolver/resolver.rs index 349d685862..ecf92d2d83 100644 --- a/crates/ty_python_semantic/src/module_resolver/resolver.rs +++ b/crates/ty_python_semantic/src/module_resolver/resolver.rs @@ -47,8 +47,33 @@ pub fn resolve_real_module<'db>(db: &'db dyn Db, module_name: &ModuleName) -> Op resolve_module_query(db, interned_name) } +/// Resolves a module name to a module (stubs not allowed, some shadowing is +/// allowed). +/// +/// In particular, this allows `typing_extensions` to be shadowed by a +/// non-standard library module. This is useful in the context of the LSP +/// where we don't want to pretend as if these modules are always available at +/// runtime. +/// +/// This should generally only be used within the context of the LSP. Using it +/// within ty proper risks being unable to resolve builtin modules since they +/// are involved in an import cycle with `builtins`. +pub fn resolve_real_shadowable_module<'db>( + db: &'db dyn Db, + module_name: &ModuleName, +) -> Option> { + let interned_name = ModuleNameIngredient::new( + db, + module_name, + ModuleResolveMode::StubsNotAllowedSomeShadowingAllowed, + ); + + resolve_module_query(db, interned_name) +} + /// Which files should be visible when doing a module query #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, get_size2::GetSize)] +#[allow(clippy::enum_variant_names)] pub(crate) enum ModuleResolveMode { /// Stubs are allowed to appear. /// @@ -61,6 +86,13 @@ pub(crate) enum ModuleResolveMode { /// implementations. When querying searchpaths this also notably replaces typeshed with /// the "real" stdlib. StubsNotAllowed, + /// Like `StubsNotAllowed`, but permits some modules to be shadowed. + /// + /// In particular, this allows `typing_extensions` to be shadowed by a + /// non-standard library module. This is useful in the context of the LSP + /// where we don't want to pretend as if these modules are always available + /// at runtime. + StubsNotAllowedSomeShadowingAllowed, } #[salsa::interned(heap_size=ruff_memory_usage::heap_size)] @@ -73,6 +105,39 @@ impl ModuleResolveMode { fn stubs_allowed(self) -> bool { matches!(self, Self::StubsAllowed) } + + /// Returns `true` if the module name refers to a standard library module + /// which can't be shadowed by a first-party module. + /// + /// This includes "builtin" modules, which can never be shadowed at runtime + /// either. Additionally, certain other modules that are involved in an + /// import cycle with `builtins` (`types`, `typing_extensions`, etc.) are + /// also considered non-shadowable, unless the module resolution mode + /// specifically opts into allowing some of them to be shadowed. This + /// latter set of modules cannot be allowed to be shadowed by first-party + /// or "extra-path" modules in ty proper, or we risk panics in unexpected + /// places due to being unable to resolve builtin symbols. This is similar + /// behaviour to other type checkers such as mypy: + /// + pub(super) fn is_non_shadowable(self, minor_version: u8, module_name: &str) -> bool { + // Builtin modules are never shadowable, no matter what. + if ruff_python_stdlib::sys::is_builtin_module(minor_version, module_name) { + return true; + } + // Similarly for `types`, which is always available at runtime. + if module_name == "types" { + return true; + } + + // Otherwise, some modules should only be conditionally allowed + // to be shadowed, depending on the module resolution mode. + match self { + ModuleResolveMode::StubsAllowed | ModuleResolveMode::StubsNotAllowed => { + module_name == "typing_extensions" + } + ModuleResolveMode::StubsNotAllowedSomeShadowingAllowed => false, + } + } } /// Salsa query that resolves an interned [`ModuleNameIngredient`] to a module. @@ -386,7 +451,10 @@ impl SearchPaths { pub(crate) fn stdlib(&self, mode: ModuleResolveMode) -> Option<&SearchPath> { match mode { ModuleResolveMode::StubsAllowed => self.stdlib_path.as_ref(), - ModuleResolveMode::StubsNotAllowed => self.real_stdlib_path.as_ref(), + ModuleResolveMode::StubsNotAllowed + | ModuleResolveMode::StubsNotAllowedSomeShadowingAllowed => { + self.real_stdlib_path.as_ref() + } } } @@ -439,7 +507,8 @@ pub(crate) fn dynamic_resolution_paths<'db>( // Use the `ModuleResolveMode` to determine which stdlib (if any) to mark as existing let stdlib = match mode.mode(db) { ModuleResolveMode::StubsAllowed => stdlib_path, - ModuleResolveMode::StubsNotAllowed => real_stdlib_path, + ModuleResolveMode::StubsNotAllowed + | ModuleResolveMode::StubsNotAllowedSomeShadowingAllowed => real_stdlib_path, }; if let Some(path) = stdlib.as_ref().and_then(SearchPath::as_system_path) { existing_paths.insert(Cow::Borrowed(path)); @@ -684,27 +753,13 @@ struct ModuleNameIngredient<'db> { pub(super) mode: ModuleResolveMode, } -/// Returns `true` if the module name refers to a standard library module which can't be shadowed -/// by a first-party module. -/// -/// This includes "builtin" modules, which can never be shadowed at runtime either, as well as -/// certain other modules that are involved in an import cycle with `builtins` (`types`, -/// `typing_extensions`, etc.). This latter set of modules cannot be allowed to be shadowed by -/// first-party or "extra-path" modules, or we risk panics in unexpected places due to being -/// unable to resolve builtin symbols. This is similar behaviour to other type checkers such -/// as mypy: -pub(super) fn is_non_shadowable(minor_version: u8, module_name: &str) -> bool { - matches!(module_name, "types" | "typing_extensions") - || ruff_python_stdlib::sys::is_builtin_module(minor_version, module_name) -} - /// Given a module name and a list of search paths in which to lookup modules, /// attempt to resolve the module name fn resolve_name(db: &dyn Db, name: &ModuleName, mode: ModuleResolveMode) -> Option { let program = Program::get(db); let python_version = program.python_version(db); let resolver_state = ResolverContext::new(db, python_version, mode); - let is_non_shadowable = is_non_shadowable(python_version.minor, name.as_str()); + let is_non_shadowable = mode.is_non_shadowable(python_version.minor, name.as_str()); let name = RelaxedModuleName::new(name); let stub_name = name.to_stub_package(); diff --git a/crates/ty_python_semantic/src/semantic_model.rs b/crates/ty_python_semantic/src/semantic_model.rs index e2c550b0b3..236305cb73 100644 --- a/crates/ty_python_semantic/src/semantic_model.rs +++ b/crates/ty_python_semantic/src/semantic_model.rs @@ -6,7 +6,6 @@ use ruff_python_parser::Parsed; use ruff_source_file::LineIndex; use rustc_hash::FxHashMap; -use crate::Db; use crate::module_name::ModuleName; use crate::module_resolver::{KnownModule, Module, list_modules, resolve_module}; use crate::semantic_index::definition::Definition; @@ -14,6 +13,7 @@ use crate::semantic_index::scope::FileScopeId; use crate::semantic_index::semantic_index; use crate::types::ide_support::{Member, all_declarations_and_bindings, all_members}; use crate::types::{Type, binding_type, infer_scope_types}; +use crate::{Db, resolve_real_shadowable_module}; /// The primary interface the LSP should use for querying semantic information about a [`File`]. /// @@ -105,8 +105,14 @@ impl<'db> SemanticModel<'db> { /// Returns completions for symbols available in a `import ` context. pub fn import_completions(&self) -> Vec> { + let typing_extensions = ModuleName::new("typing_extensions").unwrap(); + let is_typing_extensions_available = self.file.is_stub(self.db) + || resolve_real_shadowable_module(self.db, &typing_extensions).is_some(); list_modules(self.db) .into_iter() + .filter(|module| { + is_typing_extensions_available || module.name(self.db) != &typing_extensions + }) .map(|module| { let builtin = module.is_known(self.db, KnownModule::Builtins); let ty = Type::module_literal(self.db, self.file, module); diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_undefined_decorator.snap b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_undefined_decorator.snap index 576c493622..44c5c5cd22 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_undefined_decorator.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_undefined_decorator.snap @@ -3,52 +3,6 @@ source: crates/ty_server/tests/e2e/code_actions.rs expression: code_actions --- [ - { - "title": "import typing_extensions.deprecated", - "kind": "quickfix", - "diagnostics": [ - { - "range": { - "start": { - "line": 1, - "character": 1 - }, - "end": { - "line": 1, - "character": 11 - } - }, - "severity": 1, - "code": "unresolved-reference", - "codeDescription": { - "href": "https://ty.dev/rules#unresolved-reference" - }, - "source": "ty", - "message": "Name `deprecated` used when not defined", - "relatedInformation": [] - } - ], - "edit": { - "changes": { - "file:///src/foo.py": [ - { - "range": { - "start": { - "line": 0, - "character": 0 - }, - "end": { - "line": 0, - "character": 0 - } - }, - "newText": "from typing_extensions import deprecated\n" - } - ] - } - }, - "isPreferred": true - }, { "title": "import warnings.deprecated", "kind": "quickfix", diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_undefined_reference_multi.snap b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_undefined_reference_multi.snap index d081e783de..a57ac11745 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_undefined_reference_multi.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_undefined_reference_multi.snap @@ -49,52 +49,6 @@ expression: code_actions }, "isPreferred": true }, - { - "title": "import typing_extensions.Literal", - "kind": "quickfix", - "diagnostics": [ - { - "range": { - "start": { - "line": 0, - "character": 3 - }, - "end": { - "line": 0, - "character": 10 - } - }, - "severity": 1, - "code": "unresolved-reference", - "codeDescription": { - "href": "https://ty.dev/rules#unresolved-reference" - }, - "source": "ty", - "message": "Name `Literal` used when not defined", - "relatedInformation": [] - } - ], - "edit": { - "changes": { - "file:///src/foo.py": [ - { - "range": { - "start": { - "line": 0, - "character": 0 - }, - "end": { - "line": 0, - "character": 0 - } - }, - "newText": "from typing_extensions import Literal\n" - } - ] - } - }, - "isPreferred": true - }, { "title": "Ignore 'unresolved-reference' for this line", "kind": "quickfix", diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__notebook__auto_import.snap b/crates/ty_server/tests/e2e/snapshots/e2e__notebook__auto_import.snap index 0b1f8ad282..cb2f8c55e3 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__notebook__auto_import.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__notebook__auto_import.snap @@ -24,31 +24,10 @@ expression: completions } ] }, - { - "label": "Literal (import typing_extensions)", - "kind": 6, - "sortText": " 51", - "insertText": "Literal", - "additionalTextEdits": [ - { - "range": { - "start": { - "line": 1, - "character": 0 - }, - "end": { - "line": 1, - "character": 0 - } - }, - "newText": "from typing_extensions import Literal\n" - } - ] - }, { "label": "LiteralString (import typing)", "kind": 6, - "sortText": " 52", + "sortText": " 51", "insertText": "LiteralString", "additionalTextEdits": [ { @@ -65,26 +44,5 @@ expression: completions "newText": "from typing import LiteralString\n" } ] - }, - { - "label": "LiteralString (import typing_extensions)", - "kind": 6, - "sortText": " 53", - "insertText": "LiteralString", - "additionalTextEdits": [ - { - "range": { - "start": { - "line": 1, - "character": 0 - }, - "end": { - "line": 1, - "character": 0 - } - }, - "newText": "from typing_extensions import LiteralString\n" - } - ] } ] diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__notebook__auto_import_docstring.snap b/crates/ty_server/tests/e2e/snapshots/e2e__notebook__auto_import_docstring.snap index 0b1f8ad282..cb2f8c55e3 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__notebook__auto_import_docstring.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__notebook__auto_import_docstring.snap @@ -24,31 +24,10 @@ expression: completions } ] }, - { - "label": "Literal (import typing_extensions)", - "kind": 6, - "sortText": " 51", - "insertText": "Literal", - "additionalTextEdits": [ - { - "range": { - "start": { - "line": 1, - "character": 0 - }, - "end": { - "line": 1, - "character": 0 - } - }, - "newText": "from typing_extensions import Literal\n" - } - ] - }, { "label": "LiteralString (import typing)", "kind": 6, - "sortText": " 52", + "sortText": " 51", "insertText": "LiteralString", "additionalTextEdits": [ { @@ -65,26 +44,5 @@ expression: completions "newText": "from typing import LiteralString\n" } ] - }, - { - "label": "LiteralString (import typing_extensions)", - "kind": 6, - "sortText": " 53", - "insertText": "LiteralString", - "additionalTextEdits": [ - { - "range": { - "start": { - "line": 1, - "character": 0 - }, - "end": { - "line": 1, - "character": 0 - } - }, - "newText": "from typing_extensions import LiteralString\n" - } - ] } ] diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__notebook__auto_import_from_future.snap b/crates/ty_server/tests/e2e/snapshots/e2e__notebook__auto_import_from_future.snap index 0b1f8ad282..cb2f8c55e3 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__notebook__auto_import_from_future.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__notebook__auto_import_from_future.snap @@ -24,31 +24,10 @@ expression: completions } ] }, - { - "label": "Literal (import typing_extensions)", - "kind": 6, - "sortText": " 51", - "insertText": "Literal", - "additionalTextEdits": [ - { - "range": { - "start": { - "line": 1, - "character": 0 - }, - "end": { - "line": 1, - "character": 0 - } - }, - "newText": "from typing_extensions import Literal\n" - } - ] - }, { "label": "LiteralString (import typing)", "kind": 6, - "sortText": " 52", + "sortText": " 51", "insertText": "LiteralString", "additionalTextEdits": [ { @@ -65,26 +44,5 @@ expression: completions "newText": "from typing import LiteralString\n" } ] - }, - { - "label": "LiteralString (import typing_extensions)", - "kind": 6, - "sortText": " 53", - "insertText": "LiteralString", - "additionalTextEdits": [ - { - "range": { - "start": { - "line": 1, - "character": 0 - }, - "end": { - "line": 1, - "character": 0 - } - }, - "newText": "from typing_extensions import LiteralString\n" - } - ] } ] diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__notebook__auto_import_same_cell.snap b/crates/ty_server/tests/e2e/snapshots/e2e__notebook__auto_import_same_cell.snap index ac3881368b..b7d8c9907a 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__notebook__auto_import_same_cell.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__notebook__auto_import_same_cell.snap @@ -24,31 +24,10 @@ expression: completions } ] }, - { - "label": "Literal (import typing_extensions)", - "kind": 6, - "sortText": " 51", - "insertText": "Literal", - "additionalTextEdits": [ - { - "range": { - "start": { - "line": 0, - "character": 0 - }, - "end": { - "line": 0, - "character": 0 - } - }, - "newText": "from typing_extensions import Literal\n" - } - ] - }, { "label": "LiteralString (import typing)", "kind": 6, - "sortText": " 52", + "sortText": " 51", "insertText": "LiteralString", "additionalTextEdits": [ { @@ -65,26 +44,5 @@ expression: completions "newText": ", LiteralString" } ] - }, - { - "label": "LiteralString (import typing_extensions)", - "kind": 6, - "sortText": " 53", - "insertText": "LiteralString", - "additionalTextEdits": [ - { - "range": { - "start": { - "line": 0, - "character": 0 - }, - "end": { - "line": 0, - "character": 0 - } - }, - "newText": "from typing_extensions import LiteralString\n" - } - ] } ]