Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[red-knot] Diagnostic for conflicting declared attribute types #15964

Open
Tracked by #14164
sharkdp opened this issue Feb 5, 2025 · 5 comments
Open
Tracked by #14164

[red-knot] Diagnostic for conflicting declared attribute types #15964

sharkdp opened this issue Feb 5, 2025 · 5 comments
Labels
help wanted Contributions especially welcome red-knot Multi-file analysis & type inference

Comments

@sharkdp
Copy link
Contributor

sharkdp commented Feb 5, 2025

We should emit a diagnostic when we see conflicting declared types of attributes, either between the declaration in the class body and declarations in a method, or between annotated assignments in different methods. We have existing tests for this scenario (see TODO comments):

class C:
z: int
def __init__(self) -> None:
self.x = get_int()
self.y: int = 1
def other_method(self):
self.x = get_str()
# TODO: this redeclaration should be an error
self.y: str = "a"
# TODO: this redeclaration should be an error
self.z: str = "a"

part of: #14164

@sharkdp sharkdp added the red-knot Multi-file analysis & type inference label Feb 5, 2025
@sharkdp sharkdp changed the title Emit a diagnostic when we see conflicting declared types between class body and __init__ (see TODO in own_instance_member) [red-knot] Diagnostic for conflicting declared attribute types Feb 5, 2025
@sharkdp sharkdp added the help wanted Contributions especially welcome label Feb 5, 2025
@smokyabdulrahman
Copy link
Contributor

I am not sure if this is helpful, but here is what pyright reports for the mentioned test case:

class C:
    """
    Diagnostics:
    Declaration "z" is obscured by a declaration of the same name [reportRedeclaration]
    """

    z: int

    def __init__(self) -> None:
        self.x = get_int()
        """
        Diagnostics:
        Declaration "y" is obscured by a declaration of the same name [reportRedeclaration]
        """
        self.y: int = 1

    def other_method(self):
        self.x = get_str()

        self.y: str = "a"

        self.z: str = "a"

I am trying to learn/understand the technical stuff behind parsers by reading (books, blogs) and contributing to tools I love that has things to do with parsers (ruff ❤).

is this issue about adding a new rule? or just fixing/refactoring red-knot tests mentioned (modify the markdown file and mention the correct error/reveal messages)?

@sharkdp
Copy link
Contributor Author

sharkdp commented Feb 10, 2025

is this issue about adding a new rule?

Yes, this is about emitting a new diagnostic (we can probably reuse conflicting-declarations) when we see something like this.

We do want to report them in the same places where pyright reports "declaration … is obscured by …", but I think we want to use a slightly different wording, as we generally allow re-declarations of symbols:

x: int = 1
x: str = "foo"

What's not allowed though, is to have multiple visible conflicting declarations:

def _(flag: bool):
    if flag:
        x: str
    else:
        x: int

    x = 1  # error: [conflicting-declarations]

For the case mentioned in this issue, we want something similar, but for declarations of attributes.

@carljm
Copy link
Contributor

carljm commented Feb 10, 2025

(To be clear, though, this issue is not about adding a new ruff rule, it's about a new red-knot diagnostic. Although they are in the same codebase, red-knot is separate from ruff and shares only the parser/AST with it.)

@smokyabdulrahman
Copy link
Contributor

Thank you all for the information. All clear.

I will give this issue a shot.

@smokyabdulrahman
Copy link
Contributor

While trying to dive deep into the code, I've found the following comments where I think the report_lint should happen.

Err((declared_ty, _conflicting_declarations)) => {
// There are conflicting declarations for this attribute in the class body.
SymbolAndQualifiers(
Symbol::bound(declared_ty.inner_type()),
declared_ty.qualifiers(),
)
}

Also, I've found this TODO:

for attribute_assignment in attribute_assignments {
match attribute_assignment {
AttributeAssignment::Annotated { annotation } => {
// We found an annotated assignment of one of the following forms (using 'self' in these
// examples, but we support arbitrary names for the first parameters of methods):
//
// self.name: <annotation>
// self.name: <annotation> = …
let annotation_ty = infer_expression_type(db, *annotation);
// TODO: check if there are conflicting declarations
return Symbol::bound(annotation_ty);
}

However, I believe that type.rs doesn't have the context to report the diagnostic for a reason.

Could you please point me to a similar example where a similar rule had been tackled?

My thought process is to implement some logic in:

fn add_declaration(
&mut self,
node: AnyNodeRef,
declaration: Definition<'db>,
ty: TypeAndQualifiers<'db>,
) {

and:

fn add_declaration_with_binding(
&mut self,
node: AnyNodeRef,
definition: Definition<'db>,
declared_and_inferred_ty: &DeclaredAndInferredType<'db>,
) {

or to check here if the target is an instance member of class, and check all the symbol declarations in the scope of the class body, by calling symbol_from_declarations and report the conflicting declarations if any.

fn infer_annotated_assignment_definition(
&mut self,
assignment: &ast::StmtAnnAssign,
definition: Definition<'db>,
) {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Contributions especially welcome red-knot Multi-file analysis & type inference
Projects
None yet
Development

No branches or pull requests

3 participants