mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:44:56 +00:00
[ty] Support declaration-only attributes (#19048)
## Summary Following ty issue [#698](https://github.com/astral-sh/ty/issues/698) this PR adds support for declarations. closes #698 ## Test Plan Tested against mdtest (specifically attributes). --------- Co-authored-by: David Peter <mail@david-peter.de>
This commit is contained in:
parent
b6edfbc70f
commit
e0b7f496f2
4 changed files with 90 additions and 16 deletions
|
@ -124,6 +124,30 @@ pub(crate) fn attribute_assignments<'db, 's>(
|
|||
})
|
||||
}
|
||||
|
||||
/// Returns all attribute declarations (and their method scope IDs) with a symbol name matching
|
||||
/// the one given for a specific class body scope.
|
||||
///
|
||||
/// Only call this when doing type inference on the same file as `class_body_scope`, otherwise it
|
||||
/// introduces a direct dependency on that file's AST.
|
||||
pub(crate) fn attribute_declarations<'db, 's>(
|
||||
db: &'db dyn Db,
|
||||
class_body_scope: ScopeId<'db>,
|
||||
name: &'s str,
|
||||
) -> impl Iterator<Item = (DeclarationsIterator<'db, 'db>, FileScopeId)> + use<'s, 'db> {
|
||||
let file = class_body_scope.file(db);
|
||||
let index = semantic_index(db, file);
|
||||
|
||||
attribute_scopes(db, class_body_scope).filter_map(|function_scope_id| {
|
||||
let place_table = index.place_table(function_scope_id);
|
||||
let place = place_table.place_id_by_instance_attribute_name(name)?;
|
||||
let use_def = &index.use_def_maps[function_scope_id];
|
||||
Some((
|
||||
use_def.inner.all_reachable_declarations(place),
|
||||
function_scope_id,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns all attribute assignments as scope IDs for a specific class body scope.
|
||||
///
|
||||
/// Only call this when doing type inference on the same file as `class_body_scope`, otherwise it
|
||||
|
|
|
@ -509,6 +509,18 @@ impl<'db> UseDefMap<'db> {
|
|||
.is_always_false()
|
||||
}
|
||||
|
||||
pub(crate) fn is_declaration_reachable(
|
||||
&self,
|
||||
db: &dyn crate::Db,
|
||||
declaration: &DeclarationWithConstraint<'db>,
|
||||
) -> Truthiness {
|
||||
self.reachability_constraints.evaluate(
|
||||
db,
|
||||
&self.predicates,
|
||||
declaration.reachability_constraint,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn is_binding_reachable(
|
||||
&self,
|
||||
db: &dyn crate::Db,
|
||||
|
|
|
@ -11,7 +11,7 @@ use super::{
|
|||
};
|
||||
use crate::semantic_index::definition::{Definition, DefinitionState};
|
||||
use crate::semantic_index::place::NodeWithScopeKind;
|
||||
use crate::semantic_index::{DeclarationWithConstraint, SemanticIndex};
|
||||
use crate::semantic_index::{DeclarationWithConstraint, SemanticIndex, attribute_declarations};
|
||||
use crate::types::context::InferContext;
|
||||
use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE};
|
||||
use crate::types::function::{DataclassTransformerParams, KnownFunction};
|
||||
|
@ -1763,10 +1763,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
let class_map = use_def_map(db, class_body_scope);
|
||||
let class_table = place_table(db, class_body_scope);
|
||||
|
||||
for (attribute_assignments, method_scope_id) in
|
||||
attribute_assignments(db, class_body_scope, name)
|
||||
{
|
||||
let method_scope = method_scope_id.to_scope_id(db, file);
|
||||
let is_valid_scope = |method_scope: ScopeId<'db>| {
|
||||
if let Some(method_def) = method_scope.node(db).as_function(&module) {
|
||||
let method_name = method_def.name.as_str();
|
||||
if let Place::Type(Type::FunctionLiteral(method_type), _) =
|
||||
|
@ -1774,10 +1771,53 @@ impl<'db> ClassLiteral<'db> {
|
|||
{
|
||||
let method_decorator = MethodDecorator::try_from_fn_type(db, method_type);
|
||||
if method_decorator != Ok(target_method_decorator) {
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
};
|
||||
|
||||
// First check declarations
|
||||
for (attribute_declarations, method_scope_id) in
|
||||
attribute_declarations(db, class_body_scope, name)
|
||||
{
|
||||
let method_scope = method_scope_id.to_scope_id(db, file);
|
||||
if !is_valid_scope(method_scope) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for attribute_declaration in attribute_declarations {
|
||||
let DefinitionState::Defined(decl) = attribute_declaration.declaration else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let DefinitionKind::AnnotatedAssignment(annotated) = decl.kind(db) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if use_def_map(db, method_scope)
|
||||
.is_declaration_reachable(db, &attribute_declaration)
|
||||
.is_always_false()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let annotation_ty =
|
||||
infer_expression_type(db, index.expression(annotated.annotation(&module)));
|
||||
|
||||
return Place::bound(annotation_ty);
|
||||
}
|
||||
}
|
||||
|
||||
for (attribute_assignments, method_scope_id) in
|
||||
attribute_assignments(db, class_body_scope, name)
|
||||
{
|
||||
let method_scope = method_scope_id.to_scope_id(db, file);
|
||||
if !is_valid_scope(method_scope) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let method_map = use_def_map(db, method_scope);
|
||||
|
||||
// The attribute assignment inherits the reachability of the method which contains it
|
||||
|
@ -2015,6 +2055,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
|
||||
let declarations = use_def.end_of_scope_declarations(place_id);
|
||||
let declared_and_qualifiers = place_from_declarations(db, declarations);
|
||||
|
||||
match declared_and_qualifiers {
|
||||
Ok(PlaceAndQualifiers {
|
||||
place: mut declared @ Place::Type(declared_ty, declaredness),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue