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

SONARPY-2313 Fix FN on S6543 when a generic class is defined in another file #2162

Merged
merged 4 commits into from
Nov 21, 2024

Conversation

guillaume-dequenne-sonarsource
Copy link
Contributor

@guillaume-dequenne-sonarsource guillaume-dequenne-sonarsource commented Nov 18, 2024

This PR introduces the notion of a SpecialFormType.
This kind of type is a special typing internal type used to mark typing constructs (such as typing.Union, typing.Optional, etc...).

In typing.pyi, these constructs are defined as:

Union: _SpecialForm
Generic: _SpecialForm
...

Representing these constructs as instances of the ClassType SpecialForm is problematic for two reasons:

  • It introduces the risk of FPs by considering these like "normal" types
  • They would be represented as ObjectType instances of SpecialForm. Disambiguating them would be difficult (relying on attributes, which is initially not suited to store simple names).

Therefore, I believe introducing this special kind of type is simpler to circumvent these problems.

@guillaume-dequenne-sonarsource guillaume-dequenne-sonarsource force-pushed the SONARPY-2313 branch 15 times, most recently from bdaac22 to b49d901 Compare November 20, 2024 14:50
@guillaume-dequenne-sonarsource guillaume-dequenne-sonarsource changed the title SONARPY-2313 SONARPY-2313 Fix FN on S6543 when a generic class is defined in another file Nov 20, 2024
@guillaume-dequenne-sonarsource guillaume-dequenne-sonarsource marked this pull request as ready for review November 20, 2024 15:39
@@ -1,6 +1,6 @@
from typing import Any

class ParentClass(object):
class ParentClass[T](object):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: the issues were initially not raised due to ParentClass not being a proper generic class.

Copy link
Contributor

@thomas-serre-sonarsource thomas-serre-sonarsource left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job, well done! Super clean implementation 💪

Comment on lines 7 to 9
class MyImportedChild(SomeGeneric[T]): ...
class MyImportedChild2(SomeGeneric): ...
class MyImportedChild3(SomeGeneric[str]): ...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit I think it is usually clearer to give more explicit names to show the intent of the class you create even in tests.

Suggested change
class MyImportedChild(SomeGeneric[T]): ...
class MyImportedChild2(SomeGeneric): ...
class MyImportedChild3(SomeGeneric[str]): ...
class MyImportedGenericTypeVarChild(SomeGeneric[T]): ...
class MyImportedGenericChild(SomeGeneric): ...
class MyImportedConcreteChild(SomeGeneric[str]): ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed MyImportedChild2 to MyImportedNonGenericChild (negated) to be more explicit about the fact it's no longer a generic class.

String annotatedType = from.annotatedType();
if (annotatedType != null) {
if ("typing._SpecialForm".equals(annotatedType) && fullyQualifiedName != null) {
// Defensive null check on fullyQualifiedName: it should never be null for SpecialForm
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +285 to +294
if (pythonType instanceof ClassType classType && classType.isGeneric()) {
return TriBool.TRUE;
}
if (pythonType instanceof SpecialFormType specialFormType) {
return "typing.Generic".equals(specialFormType.fullyQualifiedName()) ? TriBool.TRUE : TriBool.UNKNOWN;
}
if (pythonType instanceof UnknownType.UnresolvedImportType unresolvedImportType && "typing.Generic".equals(unresolvedImportType.importPath())) {
return TriBool.TRUE;
}
return TriBool.UNKNOWN;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function never returns false. Is it a conservative choice? For example, It seems paradoxical for ClassType to have isGeneric false and to return UNKNOWN here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's conservative. I think that isGeneric in ClassType should probably be a TriBool in fact, as what I want to express is that isGeneric=true means that it's for sure a generic class, and false just means there's no sign that it is.
I don't think it should have an impact until we actually want to raise issues on explicitly non generic classes, where this heuristic will have to be thought about more deeply.

Copy link
Contributor

@thomas-serre-sonarsource thomas-serre-sonarsource left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! It is better with this last commit in my opinion. Good job

Comment on lines +1 to +8
from genericTypeWithoutArgumentImported import (
SomeGeneric,
SomeGenericWithTypeParam,
MyImportedGenericTypeVarChild,
MyImportedNonGenericChild,
MyImportedConcreteChild,
SomeGenericIncorrectlyDefined
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may be?

Suggested change
from genericTypeWithoutArgumentImported import (
SomeGeneric,
SomeGenericWithTypeParam,
MyImportedGenericTypeVarChild,
MyImportedNonGenericChild,
MyImportedConcreteChild,
SomeGenericIncorrectlyDefined
)
from genericTypeWithoutArgumentImported import *

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since support of wildcard import is brittle at the moment, I don't want to add confusion to this test: I prefer to keep the explicit import.

@guillaume-dequenne-sonarsource guillaume-dequenne-sonarsource force-pushed the SONARPY-2313 branch 3 times, most recently from 98694cc to bf2b80c Compare November 21, 2024 16:32
@guillaume-dequenne-sonarsource guillaume-dequenne-sonarsource merged commit 0a4a5b8 into master Nov 21, 2024
8 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants