[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
This commit is contained in:
Andrew Gallant 2025-12-01 09:44:31 -05:00 committed by Andrew Gallant
parent 0e651b50b7
commit a561e6659d
14 changed files with 188 additions and 297 deletions

View File

@ -25,4 +25,4 @@ scope-simple-long-identifier,main.py,0,1
tstring-completions,main.py,0,1 tstring-completions,main.py,0,1
ty-extensions-lower-stdlib,main.py,0,8 ty-extensions-lower-stdlib,main.py,0,8
type-var-typing-over-ast,main.py,0,3 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

1 name file index rank
25 tstring-completions main.py 0 1
26 ty-extensions-lower-stdlib main.py 0 8
27 type-var-typing-over-ast main.py 0 3
28 type-var-typing-over-ast main.py 1 278 275

View File

@ -1,6 +1,6 @@
use ruff_db::files::File; use ruff_db::files::File;
use ty_project::Db; 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}; 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 /// Returns symbols from all files in the workspace and dependencies, filtered
/// by the query. /// by the query.
pub fn all_symbols<'db>(db: &'db dyn Db, query: &QueryPattern) -> Vec<AllSymbolInfo<'db>> { pub fn all_symbols<'db>(
db: &'db dyn Db,
importing_from: File,
query: &QueryPattern,
) -> Vec<AllSymbolInfo<'db>> {
// If the query is empty, return immediately to avoid expensive file scanning // If the query is empty, return immediately to avoid expensive file scanning
if query.will_match_everything() { if query.will_match_everything() {
return Vec::new(); 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 results = std::sync::Mutex::new(Vec::new());
{ {
let modules = all_modules(db); let modules = all_modules(db);
@ -28,6 +36,11 @@ pub fn all_symbols<'db>(db: &'db dyn Db, query: &QueryPattern) -> Vec<AllSymbolI
let Some(file) = module.file(&*db) else { let Some(file) = module.file(&*db) else {
continue; continue;
}; };
// TODO: also make it available in `TYPE_CHECKING` blocks
// (we'd need https://github.com/astral-sh/ty/issues/1553 to do this well)
if !is_typing_extensions_available && module.name(&*db) == &typing_extensions {
continue;
}
s.spawn(move |_| { s.spawn(move |_| {
for (_, symbol) in symbols_for_file_global_only(&*db, file).search(query) { for (_, symbol) in symbols_for_file_global_only(&*db, file).search(query) {
// It seems like we could do better here than // It seems like we could do better here than
@ -143,7 +156,7 @@ ABCDEFGHIJKLMNOP = 'https://api.example.com'
impl CursorTest { impl CursorTest {
fn all_symbols(&self, query: &str) -> String { fn all_symbols(&self, query: &str) -> 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() { if symbols.is_empty() {
return "No symbols found".to_string(); return "No symbols found".to_string();

View File

@ -517,7 +517,7 @@ fn add_unimported_completions<'db>(
let importer = Importer::new(db, &stylist, file, source.as_str(), parsed); let importer = Importer::new(db, &stylist, file, source.as_str(), parsed);
let members = importer.members_in_scope_at(scoped.node, scoped.node.start()); 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) if symbol.module.file(db) == Some(file) || symbol.module.is_known(db, KnownModule::Builtins)
{ {
continue; continue;
@ -5566,10 +5566,7 @@ def foo(param: s<CURSOR>)
#[test] #[test]
fn from_import_no_space_not_suggests_import() { fn from_import_no_space_not_suggests_import() {
let builder = completion_test_builder("from typing<CURSOR>"); let builder = completion_test_builder("from typing<CURSOR>");
assert_snapshot!(builder.build().snapshot(), @r" assert_snapshot!(builder.build().snapshot(), @"typing");
typing
typing_extensions
");
} }
#[test] #[test]
@ -5785,6 +5782,86 @@ from .imp<CURSOR>
"); ");
} }
#[test]
fn typing_extensions_excluded_from_import() {
let builder = completion_test_builder("from typing<CURSOR>").module_names();
assert_snapshot!(builder.build().snapshot(), @"typing :: Current module");
}
#[test]
fn typing_extensions_excluded_from_auto_import() {
let builder = completion_test_builder("deprecated<CURSOR>")
.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<CURSOR>")
.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<CURSOR>")
.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<CURSOR>")
.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<CURSOR>")
.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 /// A way to create a simple single-file (named `main.py`) completion test
/// builder. /// builder.
/// ///

View File

@ -13,7 +13,8 @@ pub use diagnostic::add_inferred_python_version_hint_to_diagnostic;
pub use module_name::{ModuleName, ModuleNameResolutionError}; pub use module_name::{ModuleName, ModuleNameResolutionError};
pub use module_resolver::{ pub use module_resolver::{
KnownModule, Module, SearchPath, SearchPathValidationError, SearchPaths, all_modules, 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::{ pub use program::{
Program, ProgramSettings, PythonVersionFileSource, PythonVersionSource, Program, ProgramSettings, PythonVersionFileSource, PythonVersionSource,

View File

@ -8,9 +8,7 @@ use crate::program::Program;
use super::module::{Module, ModuleKind}; use super::module::{Module, ModuleKind};
use super::path::{ModulePath, SearchPath, SystemOrVendoredPathRef}; use super::path::{ModulePath, SearchPath, SystemOrVendoredPathRef};
use super::resolver::{ use super::resolver::{ModuleResolveMode, ResolverContext, resolve_file_module, search_paths};
ModuleResolveMode, ResolverContext, is_non_shadowable, resolve_file_module, search_paths,
};
/// List all available modules, including all sub-modules, sorted in lexicographic order. /// List all available modules, including all sub-modules, sorted in lexicographic order.
pub fn all_modules(db: &dyn Db) -> Vec<Module<'_>> { pub fn all_modules(db: &dyn Db) -> Vec<Module<'_>> {
@ -309,7 +307,8 @@ impl<'db> Lister<'db> {
/// Returns true if the given module name cannot be shadowable. /// Returns true if the given module name cannot be shadowable.
fn is_non_shadowable(&self, name: &ModuleName) -> bool { 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 /// Returns the Python version we want to perform module resolution

View File

@ -6,7 +6,7 @@ pub use module::Module;
pub use path::{SearchPath, SearchPathValidationError}; pub use path::{SearchPath, SearchPathValidationError};
pub use resolver::SearchPaths; pub use resolver::SearchPaths;
pub(crate) use resolver::file_to_module; 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 ruff_db::system::SystemPath;
use crate::Db; use crate::Db;

View File

@ -47,8 +47,33 @@ pub fn resolve_real_module<'db>(db: &'db dyn Db, module_name: &ModuleName) -> Op
resolve_module_query(db, interned_name) 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<Module<'db>> {
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 /// Which files should be visible when doing a module query
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, get_size2::GetSize)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, get_size2::GetSize)]
#[allow(clippy::enum_variant_names)]
pub(crate) enum ModuleResolveMode { pub(crate) enum ModuleResolveMode {
/// Stubs are allowed to appear. /// Stubs are allowed to appear.
/// ///
@ -61,6 +86,13 @@ pub(crate) enum ModuleResolveMode {
/// implementations. When querying searchpaths this also notably replaces typeshed with /// implementations. When querying searchpaths this also notably replaces typeshed with
/// the "real" stdlib. /// the "real" stdlib.
StubsNotAllowed, 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)] #[salsa::interned(heap_size=ruff_memory_usage::heap_size)]
@ -73,6 +105,39 @@ impl ModuleResolveMode {
fn stubs_allowed(self) -> bool { fn stubs_allowed(self) -> bool {
matches!(self, Self::StubsAllowed) 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:
/// <https://github.com/python/mypy/blob/3807423e9d98e678bf16b13ec8b4f909fe181908/mypy/build.py#L104-L117>
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. /// 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> { pub(crate) fn stdlib(&self, mode: ModuleResolveMode) -> Option<&SearchPath> {
match mode { match mode {
ModuleResolveMode::StubsAllowed => self.stdlib_path.as_ref(), 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 // Use the `ModuleResolveMode` to determine which stdlib (if any) to mark as existing
let stdlib = match mode.mode(db) { let stdlib = match mode.mode(db) {
ModuleResolveMode::StubsAllowed => stdlib_path, 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) { if let Some(path) = stdlib.as_ref().and_then(SearchPath::as_system_path) {
existing_paths.insert(Cow::Borrowed(path)); existing_paths.insert(Cow::Borrowed(path));
@ -684,27 +753,13 @@ struct ModuleNameIngredient<'db> {
pub(super) mode: ModuleResolveMode, 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: <https://github.com/python/mypy/blob/3807423e9d98e678bf16b13ec8b4f909fe181908/mypy/build.py#L104-L117>
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, /// Given a module name and a list of search paths in which to lookup modules,
/// attempt to resolve the module name /// attempt to resolve the module name
fn resolve_name(db: &dyn Db, name: &ModuleName, mode: ModuleResolveMode) -> Option<ResolvedName> { fn resolve_name(db: &dyn Db, name: &ModuleName, mode: ModuleResolveMode) -> Option<ResolvedName> {
let program = Program::get(db); let program = Program::get(db);
let python_version = program.python_version(db); let python_version = program.python_version(db);
let resolver_state = ResolverContext::new(db, python_version, mode); 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 name = RelaxedModuleName::new(name);
let stub_name = name.to_stub_package(); let stub_name = name.to_stub_package();

View File

@ -6,7 +6,6 @@ use ruff_python_parser::Parsed;
use ruff_source_file::LineIndex; use ruff_source_file::LineIndex;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use crate::Db;
use crate::module_name::ModuleName; use crate::module_name::ModuleName;
use crate::module_resolver::{KnownModule, Module, list_modules, resolve_module}; use crate::module_resolver::{KnownModule, Module, list_modules, resolve_module};
use crate::semantic_index::definition::Definition; use crate::semantic_index::definition::Definition;
@ -14,6 +13,7 @@ use crate::semantic_index::scope::FileScopeId;
use crate::semantic_index::semantic_index; use crate::semantic_index::semantic_index;
use crate::types::ide_support::{Member, all_declarations_and_bindings, all_members}; use crate::types::ide_support::{Member, all_declarations_and_bindings, all_members};
use crate::types::{Type, binding_type, infer_scope_types}; 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`]. /// 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 <CURSOR>` context. /// Returns completions for symbols available in a `import <CURSOR>` context.
pub fn import_completions(&self) -> Vec<Completion<'db>> { pub fn import_completions(&self) -> Vec<Completion<'db>> {
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) list_modules(self.db)
.into_iter() .into_iter()
.filter(|module| {
is_typing_extensions_available || module.name(self.db) != &typing_extensions
})
.map(|module| { .map(|module| {
let builtin = module.is_known(self.db, KnownModule::Builtins); let builtin = module.is_known(self.db, KnownModule::Builtins);
let ty = Type::module_literal(self.db, self.file, module); let ty = Type::module_literal(self.db, self.file, module);

View File

@ -3,52 +3,6 @@ source: crates/ty_server/tests/e2e/code_actions.rs
expression: code_actions 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://<temp_dir>/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", "title": "import warnings.deprecated",
"kind": "quickfix", "kind": "quickfix",

View File

@ -49,52 +49,6 @@ expression: code_actions
}, },
"isPreferred": true "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://<temp_dir>/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", "title": "Ignore 'unresolved-reference' for this line",
"kind": "quickfix", "kind": "quickfix",

View File

@ -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)", "label": "LiteralString (import typing)",
"kind": 6, "kind": 6,
"sortText": " 52", "sortText": " 51",
"insertText": "LiteralString", "insertText": "LiteralString",
"additionalTextEdits": [ "additionalTextEdits": [
{ {
@ -65,26 +44,5 @@ expression: completions
"newText": "from typing import LiteralString\n" "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"
}
]
} }
] ]

View File

@ -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)", "label": "LiteralString (import typing)",
"kind": 6, "kind": 6,
"sortText": " 52", "sortText": " 51",
"insertText": "LiteralString", "insertText": "LiteralString",
"additionalTextEdits": [ "additionalTextEdits": [
{ {
@ -65,26 +44,5 @@ expression: completions
"newText": "from typing import LiteralString\n" "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"
}
]
} }
] ]

View File

@ -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)", "label": "LiteralString (import typing)",
"kind": 6, "kind": 6,
"sortText": " 52", "sortText": " 51",
"insertText": "LiteralString", "insertText": "LiteralString",
"additionalTextEdits": [ "additionalTextEdits": [
{ {
@ -65,26 +44,5 @@ expression: completions
"newText": "from typing import LiteralString\n" "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"
}
]
} }
] ]

View File

@ -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)", "label": "LiteralString (import typing)",
"kind": 6, "kind": 6,
"sortText": " 52", "sortText": " 51",
"insertText": "LiteralString", "insertText": "LiteralString",
"additionalTextEdits": [ "additionalTextEdits": [
{ {
@ -65,26 +44,5 @@ expression: completions
"newText": ", LiteralString" "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"
}
]
} }
] ]