[ty] Support 'dangling' type(...) constructors#22537
Conversation
Diagnostic diff on typing conformance testsNo changes detected when running ty on typing conformance tests ✅ |
|
type(...) constructors
Merging this PR will not alter performance
Comparing Footnotes
|
8df0d85 to
dd2fbb3
Compare
598d059 to
597c354
Compare
389c7bf to
17aa09f
Compare
I'm actually coming to the conclusion that we can't use tracked structs:
I'm not sure yet what the right solution here is but I don't feel confident building on something that is very likely unsupported in the near future. I've to think about this a little more. |
|
The only other idea I had was to create a stable ID for the call: diff --git a/crates/ty_python_semantic/src/semantic_index/ast_ids.rs b/crates/ty_python_semantic/src/semantic_index/ast_ids.rs
index cc2c65526e..514984674f 100644
--- a/crates/ty_python_semantic/src/semantic_index/ast_ids.rs
+++ b/crates/ty_python_semantic/src/semantic_index/ast_ids.rs
@@ -1,8 +1,8 @@
-use rustc_hash::FxHashMap;
-
-use ruff_index::newtype_index;
+use ruff_index::{IndexVec, newtype_index};
use ruff_python_ast as ast;
use ruff_python_ast::ExprRef;
+use ruff_text_size::TextRange;
+use rustc_hash::FxHashMap;
use crate::Db;
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey;
@@ -28,12 +28,27 @@ use crate::semantic_index::semantic_index;
pub(crate) struct AstIds {
/// Maps expressions which "use" a place (that is, [`ast::ExprName`], [`ast::ExprAttribute`] or [`ast::ExprSubscript`]) to a use id.
uses_map: FxHashMap<ExpressionNodeKey, ScopedUseId>,
+ /// Maps potential synthesized-type call expressions to a call id for stable identity.
+ tracked_calls_map: FxHashMap<ExpressionNodeKey, ScopedCallId>,
+ /// Stores the ranges of tracked calls, indexed by their [`ScopedCallId`].
+ /// Used for diagnostics (e.g., `header_range`).
+ tracked_call_ranges: IndexVec<ScopedCallId, TextRange>,
}
impl AstIds {
fn use_id(&self, key: impl Into<ExpressionNodeKey>) -> ScopedUseId {
self.uses_map[&key.into()]
}
+
+ /// Returns the call ID for a potential synthesized-type call, if it was tracked during semantic indexing.
+ pub(crate) fn try_call_id(&self, key: impl Into<ExpressionNodeKey>) -> Option<ScopedCallId> {
+ self.tracked_calls_map.get(&key.into()).copied()
+ }
+
+ /// Returns the range of a tracked call by its ID.
+ pub(crate) fn call_range(&self, id: ScopedCallId) -> TextRange {
+ self.tracked_call_ranges[id]
+ }
}
fn ast_ids<'db>(db: &'db dyn Db, scope: ScopeId) -> &'db AstIds {
@@ -45,6 +60,15 @@ fn ast_ids<'db>(db: &'db dyn Db, scope: ScopeId) -> &'db AstIds {
#[derive(get_size2::GetSize)]
pub struct ScopedUseId;
+/// Uniquely identifies a potential synthesized-type call in a [`crate::semantic_index::FileScopeId`].
+///
+/// This is used to provide stable identity for inline calls that create synthesized types,
+/// such as `type()`, `NamedTuple()`, `TypedDict()`, etc. The ID is assigned during semantic
+/// indexing for calls that match known patterns for these synthesizers.
+#[newtype_index]
+#[derive(get_size2::GetSize)]
+pub struct ScopedCallId;
+
pub trait HasScopedUseId {
/// Returns the ID that uniquely identifies the use in `scope`.
fn scoped_use_id(&self, db: &dyn Db, scope: ScopeId) -> ScopedUseId;
@@ -88,6 +112,8 @@ impl HasScopedUseId for ast::ExprRef<'_> {
#[derive(Debug, Default)]
pub(super) struct AstIdsBuilder {
uses_map: FxHashMap<ExpressionNodeKey, ScopedUseId>,
+ tracked_calls_map: FxHashMap<ExpressionNodeKey, ScopedCallId>,
+ tracked_call_ranges: IndexVec<ScopedCallId, TextRange>,
}
impl AstIdsBuilder {
@@ -100,11 +126,25 @@ impl AstIdsBuilder {
use_id
}
+ /// Records a potential synthesized-type call for stable identity tracking.
+ pub(super) fn record_call(
+ &mut self,
+ expr: impl Into<ExpressionNodeKey>,
+ range: TextRange,
+ ) -> ScopedCallId {
+ let call_id = self.tracked_call_ranges.push(range);
+ self.tracked_calls_map.insert(expr.into(), call_id);
+ call_id
+ }
+
pub(super) fn finish(mut self) -> AstIds {
self.uses_map.shrink_to_fit();
+ self.tracked_calls_map.shrink_to_fit();
AstIds {
uses_map: self.uses_map,
+ tracked_calls_map: self.tracked_calls_map,
+ tracked_call_ranges: self.tracked_call_ranges,
}
}
}
diff --git a/crates/ty_python_semantic/src/semantic_index/builder.rs b/crates/ty_python_semantic/src/semantic_index/builder.rs
index 3ef83e97d2..61945b5681 100644
--- a/crates/ty_python_semantic/src/semantic_index/builder.rs
+++ b/crates/ty_python_semantic/src/semantic_index/builder.rs
@@ -14,7 +14,7 @@ use ruff_python_ast::{self as ast, NodeIndex, PySourceType, PythonVersion};
use ruff_python_parser::semantic_errors::{
SemanticSyntaxChecker, SemanticSyntaxContext, SemanticSyntaxError, SemanticSyntaxErrorKind,
};
-use ruff_text_size::TextRange;
+use ruff_text_size::{Ranged, TextRange};
use ty_module_resolver::{ModuleName, resolve_module};
use crate::ast_node_ref::AstNodeRef;
@@ -2741,6 +2741,17 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
}
walk_expr(self, expr);
}
+ ast::Expr::Call(call_expr) => {
+ // Track potential synthesized-type calls for stable identity.
+ // Assigned calls use `Definition` for identity; inline calls need a `ScopedCallId`.
+ if self.current_assignment().is_none()
+ && is_potential_synthesized_type_call(call_expr)
+ {
+ self.current_ast_ids()
+ .record_call(call_expr, call_expr.range());
+ }
+ walk_expr(self, expr);
+ }
_ => {
walk_expr(self, expr);
}
@@ -3195,3 +3206,31 @@ fn is_if_not_type_checking(expr: &ast::Expr) -> bool {
}) if is_if_type_checking(operand)
)
}
+
+/// Returns whether a call expression might create a synthesized type.
+///
+/// This is a heuristic used during semantic indexing to assign stable IDs
+/// to calls that may produce `NamedTuple`, `TypedDict`, `type()` classes, etc.
+/// False positives are acceptable (the ID just won't be used during inference).
+fn is_potential_synthesized_type_call(call: &ast::ExprCall) -> bool {
+ // Check for `type(...)` or `builtins.type(...)`
+ let is_type_call = match call.func.as_ref() {
+ ast::Expr::Name(name) => name.id.as_str() == "type",
+ ast::Expr::Attribute(attr) => {
+ attr.attr.as_str() == "type"
+ && matches!(attr.value.as_ref(), ast::Expr::Name(name) if name.id.as_str() == "builtins")
+ }
+ _ => false,
+ };
+
+ if is_type_call {
+ // type("Name", bases, dict)
+ return call.arguments.keywords.is_empty() && call.arguments.args.len() == 3;
+ }
+
+ // TODO: Add more patterns as we support them:
+ // - NamedTuple("Name", [...]) or NamedTuple("Name", field1=type1, ...)
+ // - TypedDict("Name", {...}) or TypedDict("Name", field1=type1, ...)
+
+ false
+}
diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs
index 76cc4684b1..4c9df1bf3c 100644
--- a/crates/ty_python_semantic/src/types.rs
+++ b/crates/ty_python_semantic/src/types.rs
@@ -6529,9 +6529,9 @@ impl<'db> Type<'db> {
Some(TypeDefinition::Function(function.definition(db)))
}
Self::ModuleLiteral(module) => Some(TypeDefinition::Module(module.module(db))),
- Self::ClassLiteral(class_literal) => Some(class_literal.type_definition(db)),
+ Self::ClassLiteral(class_literal) => class_literal.type_definition(db),
Self::GenericAlias(alias) => Some(TypeDefinition::StaticClass(alias.definition(db))),
- Self::NominalInstance(instance) => Some(instance.class(db).type_definition(db)),
+ Self::NominalInstance(instance) => instance.class(db).type_definition(db),
Self::KnownInstance(instance) => match instance {
KnownInstanceType::TypeVar(var) => {
Some(TypeDefinition::TypeVar(var.definition(db)?))
@@ -6545,7 +6545,7 @@ impl<'db> Type<'db> {
Self::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
SubclassOfInner::Dynamic(_) => None,
- SubclassOfInner::Class(class) => Some(class.type_definition(db)),
+ SubclassOfInner::Class(class) => class.type_definition(db),
SubclassOfInner::TypeVar(bound_typevar) => {
Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?))
}
@@ -6575,7 +6575,7 @@ impl<'db> Type<'db> {
Self::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)),
Self::ProtocolInstance(protocol) => match protocol.inner {
- Protocol::FromClass(class) => Some(class.type_definition(db)),
+ Protocol::FromClass(class) => class.type_definition(db),
Protocol::Synthesized(_) => None,
},
diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs
index ba4f3b61fc..0d4ed7360a 100644
--- a/crates/ty_python_semantic/src/types/class.rs
+++ b/crates/ty_python_semantic/src/types/class.rs
@@ -10,11 +10,13 @@ use super::{
function::FunctionType,
};
use crate::place::{DefinedPlace, TypeOrigin};
-use crate::semantic_index::definition::{Definition, DefinitionState};
-use crate::semantic_index::scope::{NodeWithScopeKind, Scope, ScopeKind};
+use crate::semantic_index::ast_ids::ScopedCallId;
+use crate::semantic_index::definition::{Definition, DefinitionKind, DefinitionState};
+use crate::semantic_index::scope::{FileScopeId, NodeWithScopeKind, Scope, ScopeKind};
use crate::semantic_index::symbol::Symbol;
use crate::semantic_index::{
DeclarationWithConstraint, SemanticIndex, attribute_declarations, attribute_scopes,
+ semantic_index,
};
use crate::types::bound_super::BoundSuperError;
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
@@ -57,11 +59,7 @@ use crate::{
known_module_symbol, place_from_bindings, place_from_declarations,
},
semantic_index::{
- attribute_assignments,
- definition::{DefinitionKind, TargetKind},
- place_table,
- scope::{FileScopeId, ScopeId},
- semantic_index, use_def_map,
+ attribute_assignments, definition::TargetKind, place_table, scope::ScopeId, use_def_map,
},
types::{
CallArguments, CallError, CallErrorKind, MetaclassCandidate, TypeDefinition, UnionType,
@@ -668,10 +666,10 @@ impl<'db> ClassLiteral<'db> {
}
}
- /// Returns the definition of this class.
- pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
+ /// Returns the definition of this class, if available.
+ pub(crate) fn definition(self, db: &'db dyn Db) -> Option<Definition<'db>> {
match self {
- Self::Static(class) => class.definition(db),
+ Self::Static(class) => Some(class.definition(db)),
Self::Dynamic(class) => class.definition(db),
}
}
@@ -679,11 +677,11 @@ impl<'db> ClassLiteral<'db> {
/// Returns the type definition for this class.
///
/// For static classes, returns `TypeDefinition::StaticClass`.
- /// For dynamic classes, returns `TypeDefinition::DynamicClass`.
- pub(crate) fn type_definition(self, db: &'db dyn Db) -> TypeDefinition<'db> {
+ /// For dynamic classes, returns `TypeDefinition::DynamicClass` if a definition is available.
+ pub(crate) fn type_definition(self, db: &'db dyn Db) -> Option<TypeDefinition<'db>> {
match self {
- Self::Static(class) => TypeDefinition::StaticClass(class.definition(db)),
- Self::Dynamic(class) => TypeDefinition::DynamicClass(class.definition(db)),
+ Self::Static(class) => Some(TypeDefinition::StaticClass(class.definition(db))),
+ Self::Dynamic(class) => class.definition(db).map(TypeDefinition::DynamicClass),
}
}
@@ -941,13 +939,13 @@ impl<'db> ClassType<'db> {
self.class_literal(db).known(db)
}
- /// Returns the definition for this class.
- pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
+ /// Returns the definition for this class, if available.
+ pub(crate) fn definition(self, db: &'db dyn Db) -> Option<Definition<'db>> {
self.class_literal(db).definition(db)
}
/// Returns the type definition for this class.
- pub(crate) fn type_definition(self, db: &'db dyn Db) -> TypeDefinition<'db> {
+ pub(crate) fn type_definition(self, db: &'db dyn Db) -> Option<TypeDefinition<'db>> {
self.class_literal(db).type_definition(db)
}
@@ -4680,21 +4678,18 @@ impl<'db> VarianceInferable<'db> for ClassLiteral<'db> {
///
/// # Salsa interning
///
-/// Each `type()` call is uniquely identified by its [`Definition`], which provides
-/// stable identity without depending on AST node indices that can change when code
-/// is inserted above the call site.
+/// Each `type()` call is uniquely identified by its [`DynamicClassOrigin`], which provides
+/// stable identity without depending on AST node indices that can change when code is
+/// inserted above the call site.
///
-/// Two different `type()` calls always produce distinct `DynamicClassLiteral`
-/// instances, even if they have the same name and bases:
+/// Two different `type()` calls always produce distinct `DynamicClassLiteral` instances,
+/// even if they have the same name and bases:
///
/// ```python
/// Foo1 = type("Foo", (Base,), {})
/// Foo2 = type("Foo", (Base,), {})
/// # Foo1 and Foo2 are distinct types
/// ```
-///
-/// Note: Only assigned `type()` calls are currently supported (e.g., `Foo = type(...)`).
-/// Inline calls like `process(type(...))` fall back to normal call handling.
#[salsa::interned(debug, heap_size = ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct DynamicClassLiteral<'db> {
@@ -4706,14 +4701,38 @@ pub struct DynamicClassLiteral<'db> {
#[returns(deref)]
pub bases: Box<[ClassBase<'db>]>,
- /// The definition where this class is created.
- pub definition: Definition<'db>,
+ /// The origin of this dynamic class, providing stable identity.
+ pub origin: DynamicClassOrigin<'db>,
/// Dataclass parameters if this class has been wrapped with `@dataclass` decorator
/// or passed to `dataclass()` as a function.
pub dataclass_params: Option<DataclassParams<'db>>,
}
+/// The origin of a dynamically created class, used for stable identity in Salsa.
+///
+/// This enum provides stable identification for `type()` calls without relying on
+/// AST node indices that change when code is inserted above the call site.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
+pub enum DynamicClassOrigin<'db> {
+ /// A `type()` call that is assigned to a variable (e.g., `Foo = type("Foo", (), {})`).
+ ///
+ /// The Definition provides stable identity via its `ScopedPlaceId`.
+ Assigned(Definition<'db>),
+
+ /// An inline `type()` call not assigned to a variable (e.g., `process(type("Foo", (), {}))`).
+ ///
+ /// Uses file, scope, and a scoped call ID for stable identity.
+ /// The ID is assigned sequentially during semantic indexing.
+ Inline {
+ file: File,
+ file_scope: FileScopeId,
+ call_id: ScopedCallId,
+ },
+}
+
+impl get_size2::GetSize for DynamicClassOrigin<'_> {}
+
impl get_size2::GetSize for DynamicClassLiteral<'_> {}
#[salsa::tracked]
@@ -4727,25 +4746,52 @@ impl<'db> DynamicClassLiteral<'db> {
/// Returns the range of the `type()` call expression that created this class.
pub(super) fn header_range(self, db: &'db dyn Db) -> TextRange {
- let definition = self.definition(db);
- let file = definition.file(db);
- let module = parsed_module(db, file).load(db);
-
- // Dynamic classes are only created from regular assignments (e.g., `Foo = type(...)`).
- let DefinitionKind::Assignment(assignment) = definition.kind(db) else {
- unreachable!("DynamicClassLiteral should only be created from Assignment definitions");
- };
- assignment.value(&module).range()
+ match self.origin(db) {
+ DynamicClassOrigin::Assigned(definition) => {
+ // For assigned calls, get the range from the assignment value.
+ let file = definition.file(db);
+ let module = parsed_module(db, file).load(db);
+ let DefinitionKind::Assignment(assignment) = definition.kind(db) else {
+ unreachable!(
+ "DynamicClassOrigin::Assigned should only be created from Assignment definitions"
+ );
+ };
+ assignment.value(&module).range()
+ }
+ DynamicClassOrigin::Inline {
+ file,
+ file_scope,
+ call_id,
+ } => {
+ // For inline calls, look up the range from the semantic index.
+ let index = semantic_index(db, file);
+ index.ast_ids(file_scope).call_range(call_id)
+ }
+ }
}
/// Returns the file containing the `type()` call.
pub(crate) fn file(self, db: &'db dyn Db) -> File {
- self.definition(db).file(db)
+ match self.origin(db) {
+ DynamicClassOrigin::Assigned(definition) => definition.file(db),
+ DynamicClassOrigin::Inline { file, .. } => file,
+ }
}
/// Returns the scope containing the `type()` call.
pub(crate) fn file_scope(self, db: &'db dyn Db) -> FileScopeId {
- self.definition(db).file_scope(db)
+ match self.origin(db) {
+ DynamicClassOrigin::Assigned(definition) => definition.file_scope(db),
+ DynamicClassOrigin::Inline { file_scope, .. } => file_scope,
+ }
+ }
+
+ /// Returns the definition where this class is created, if in an assignment context.
+ pub(crate) fn definition(self, db: &'db dyn Db) -> Option<Definition<'db>> {
+ match self.origin(db) {
+ DynamicClassOrigin::Assigned(definition) => Some(definition),
+ DynamicClassOrigin::Inline { .. } => None,
+ }
}
/// Get the metaclass of this dynamic class.
@@ -4919,7 +4965,7 @@ impl<'db> DynamicClassLiteral<'db> {
db,
self.name(db).clone(),
self.bases(db),
- self.definition(db),
+ self.origin(db),
dataclass_params,
)
}
diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs
index b467f08bbe..290daac22a 100644
--- a/crates/ty_python_semantic/src/types/generics.rs
+++ b/crates/ty_python_semantic/src/types/generics.rs
@@ -96,7 +96,7 @@ pub(crate) fn typing_self<'db>(
let identity = TypeVarIdentity::new(
db,
ast::name::Name::new_static("Self"),
- Some(class.definition(db)),
+ class.definition(db),
TypeVarKind::TypingSelf,
);
let bounds = TypeVarBoundOrConstraints::UpperBound(Type::instance(
diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs
index 70fa611c77..73497948b1 100644
--- a/crates/ty_python_semantic/src/types/ide_support.rs
+++ b/crates/ty_python_semantic/src/types/ide_support.rs
@@ -169,9 +169,9 @@ pub fn definitions_for_name<'db>(
// instead of `int` (hover only shows the docstring of the first definition).
.rev()
.filter_map(|ty| ty.as_nominal_instance())
- .map(|instance| {
- let definition = instance.class_literal(db).definition(db);
- ResolvedDefinition::Definition(definition)
+ .filter_map(|instance| {
+ let definition = instance.class_literal(db).definition(db)?;
+ Some(ResolvedDefinition::Definition(definition))
})
.collect();
}
diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs
index 3953e4cbb8..1046d70bec 100644
--- a/crates/ty_python_semantic/src/types/infer/builder.rs
+++ b/crates/ty_python_semantic/src/types/infer/builder.rs
@@ -48,14 +48,14 @@ use crate::semantic_index::scope::{
};
use crate::semantic_index::symbol::{ScopedSymbolId, Symbol};
use crate::semantic_index::{
- ApplicableConstraints, EnclosingSnapshotResult, SemanticIndex, place_table,
+ ApplicableConstraints, EnclosingSnapshotResult, SemanticIndex, place_table, semantic_index,
};
use crate::subscript::{PyIndex, PySlice};
use crate::types::call::bind::{CallableDescription, MatchingOverloadIndex};
use crate::types::call::{Binding, Bindings, CallArguments, CallError, CallErrorKind};
use crate::types::class::{
- ClassLiteral, CodeGeneratorKind, DynamicClassLiteral, DynamicMetaclassConflict, FieldKind,
- MetaclassErrorKind, MethodDecorator,
+ ClassLiteral, CodeGeneratorKind, DynamicClassLiteral, DynamicClassOrigin,
+ DynamicMetaclassConflict, FieldKind, MetaclassErrorKind, MethodDecorator,
};
use crate::types::context::{InNoTypeCheck, InferContext};
use crate::types::cyclic::CycleDetector;
@@ -5412,7 +5412,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// Try to extract the dynamic class with definition.
// This returns `None` if it's not a three-arg call to `type()`,
// signalling that we must fall back to normal call inference.
- self.infer_dynamic_type_expression(call_expr, definition)
+ self.infer_dynamic_type_expression(call_expr, Some(definition))
.unwrap_or_else(|| {
self.infer_call_expression_impl(call_expr, callable_type, tcx)
})
@@ -6031,7 +6031,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
fn infer_dynamic_type_expression(
&mut self,
call_expr: &ast::ExprCall,
- definition: Definition<'db>,
+ definition: Option<Definition<'db>>,
) -> Option<Type<'db>> {
let db = self.db();
@@ -6090,7 +6090,29 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let bases = self.extract_dynamic_type_bases(bases_arg, bases_type, &name);
- let dynamic_class = DynamicClassLiteral::new(db, name, bases, definition, None);
+ // Get the origin for this dynamic class.
+ let origin = if let Some(def) = definition {
+ // Assigned call: use the definition for stable identity.
+ DynamicClassOrigin::Assigned(def)
+ } else {
+ // Inline call: look up the ScopedCallId from semantic indexing.
+ let file = self.file();
+ let file_scope = self.scope().file_scope_id(db);
+ let index = semantic_index(db, file);
+ let Some(call_id) = index.ast_ids(file_scope).try_call_id(call_expr) else {
+ // If no call ID was tracked for this call during semantic indexing,
+ // we can't create a stable DynamicClassLiteral. Fall back to regular
+ // type inference.
+ return None;
+ };
+ DynamicClassOrigin::Inline {
+ file,
+ file_scope,
+ call_id,
+ }
+ };
+
+ let dynamic_class = DynamicClassLiteral::new(db, name, bases, origin, None);
// Check for MRO errors.
if let Err(error) = dynamic_class.try_mro(db) {
@@ -9073,6 +9095,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
return Type::TypedDict(typed_dict);
}
+ // Handle 3-argument `type(name, bases, dict)`.
+ if let Type::ClassLiteral(class) = callable_type
+ && class.is_known(self.db(), KnownClass::Type)
+ && let Some(dynamic_type) = self.infer_dynamic_type_expression(call_expression, None)
+ {
+ return dynamic_type;
+ }
+
// We don't call `Type::try_call`, because we want to perform type inference on the
// arguments after matching them to parameters, but before checking that the argument types
// are assignable to any parameter annotations.
diff --git a/crates/ty_python_semantic/src/types/typed_dict.rs b/crates/ty_python_semantic/src/types/typed_dict.rs
index d7a93d8bcf..66543694ad 100644
--- a/crates/ty_python_semantic/src/types/typed_dict.rs
+++ b/crates/ty_python_semantic/src/types/typed_dict.rs
@@ -303,14 +303,14 @@ impl<'db> TypedDictType<'db> {
pub fn definition(self, db: &'db dyn Db) -> Option<Definition<'db>> {
match self {
- TypedDictType::Class(defining_class) => Some(defining_class.definition(db)),
+ TypedDictType::Class(defining_class) => defining_class.definition(db),
TypedDictType::Synthesized(_) => None,
}
}
pub fn type_definition(self, db: &'db dyn Db) -> Option<TypeDefinition<'db>> {
match self {
- TypedDictType::Class(defining_class) => Some(defining_class.type_definition(db)),
+ TypedDictType::Class(defining_class) => defining_class.type_definition(db),
TypedDictType::Synthesized(_) => None,
}
} |
17aa09f to
56b1d6f
Compare
I don't think this works for ty as it has false-negatives if you alias an import like We need to come up with another identifier that changes less often than an absolute node index or a text range, to limit the blast radius. There's really just been one pattern that we've been using for this and it is to make IDs relative to some Anchor other than the file root.
To unblock this feature, I'm leaning towards doing the following:
|
| self.file(db), | ||
| self.file_scope(db), |
There was a problem hiding this comment.
I'd be inclined to store ScopeId here which contains both the File and FileScopeId
|
Putting this back to draft to make it easier for reviewers to know when this is ready for review (and not one of GitHub's force push notifications ;)) |
Do we need to store |
It depends on what you want to highlight. If not, that's even better. |
56b1d6f to
fbd2949
Compare
|
(Not ready for review, I will mark it as such.) |
3bf7fa3 to
e7f60c2
Compare
MichaReiser
left a comment
There was a problem hiding this comment.
Thank you. The DynamicClassLiteral definition looks good to me. I didn't review the semantic typing changes.
e7f60c2 to
a94d0ed
Compare
Using tracked struct in cyclic queries is [broken in Salsa](#22537 (comment)). This PR updates `TrackedConstraintSet` to be interned instead. --------- Co-authored-by: Douglas Creager <[email protected]>
Using tracked struct in cyclic queries is [broken in Salsa](#22537 (comment)). This PR updates `TrackedConstraintSet` to be interned instead. --------- Co-authored-by: Douglas Creager <[email protected]>
Summary
This PR adds support for 'dangling'
type(...)constructors, e.g.:As opposed to:
The former doesn't have a
Definitionsince it doesn't get bound to a place, so we instead need to store theNodeIndex. Per @MichaReiser's suggestion, we can use a Salsa tracked struct for this.