-
-
Notifications
You must be signed in to change notification settings - Fork 821
Add NullableToStringCall rule #2903
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
Conversation
Codecov Report
@@ Coverage Diff @@
## master #2903 +/- ##
============================================
+ Coverage 80.24% 80.26% +0.01%
- Complexity 2455 2467 +12
============================================
Files 422 423 +1
Lines 7420 7452 +32
Branches 1357 1367 +10
============================================
+ Hits 5954 5981 +27
+ Misses 762 759 -3
- Partials 704 712 +8
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your contribution @t-kameyama 🙏
There are several things that need to be addressed. Moreover, I advice for splitting this PR in maybe two.
Ideally, the rule should be simplified:
- A simpler rule should just check if you're invoking
kotlin.Any?.toString(). If so, report to the user that you can potentially return a"null". - Ideally the string template handling should be added afterwards. Potentially this can also be a separate rule as the name
NullableToStringCallbecomes confusing. Something likeNullableStringInTemplateor something along this line.
|
|
||
| open val ruleId: RuleId = javaClass.simpleName | ||
| var bindingContext: BindingContext = BindingContext.EMPTY | ||
| var languageVersionSettings: LanguageVersionSettings? = null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unsure we want to have a LanguageVersionSettings stored in the baserule + extending to API to always pass it around just for a single rule.
If possible, let's come up with a simpler version of this rule that doesn't need a LanguageVersionSettings explicitly. We can still update BaseRule if it brings value, but it should probably be discussed with the other maintainers first.
| val descriptor = expression.descriptor() ?: return | ||
| val originalType = descriptor.returnType ?.takeIf { it.isNullable() } ?: return | ||
| @Suppress("DEPRECATION") | ||
| val dataFlowValueFactory = DataFlowValueFactoryImpl(languageVersionSettings) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class is deprecated and has a clear deprecation message on top:
https://github.com/JetBrains/kotlin/blob/687d13a320b4a5697aedca3ed31f6568a40deafa/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueFactoryImpl.kt#L28
I don't think we want to directly depend on this class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can get a container with the following function and get an instance of DataFlowValueFactory from it.
https://github.com/JetBrains/kotlin/blob/017f640f26e29f1b1d0909d89e812aca9173691c/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/TopDownAnalyzerFacadeForJVM.kt#L130
However, I'm not using a container here because we only need an instance of DataFlowValueFactory.
| * | ||
| * <compliant> | ||
| * fun foo(a: Any?): String { | ||
| * return a?.toString ?: "-" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * return a?.toString ?: "-" | |
| * return a?.toString() ?: "-" |
| """ | ||
| val actual = subject.compileAndLintWithContext(env, code) | ||
| Assertions.assertThat(actual).hasSize(1) | ||
| Assertions.assertThat(actual.first().issue.id).isEqualTo("NullableToStringCall") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can remove all the Assertions.assertThat(actual.first().issue.id)... as your subject contains only NullableToStringCall
| it("reports when a nullable toString is implicitly called in a raw string template") { | ||
| val code = """ | ||
| fun test(a: Any?) { | ||
| println(${'"'}${'"'}${'"'}${'$'}a${'"'}${'"'}${'"'}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels like this test is a bit too complicated. I'm having a hard time finding a real-world scenario where such code would be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code just test println("""$a""") so ${'"'}""${'$'}a""${'"'} should be enought.
|
Why do you need
Why do you think that this should be two different rules? I mean, it's the same error. When you do this: |
I don't have a strong opinion on having two separate rules here. On the other hand I do believe the explicit/implicit |
We don't want the following cases to be false positives. fun test(a: Any?) {
if (a != null) {
println("$a") // 'a' is cast to 'String'
}
}To do so, we need to know if the variables have been smart cast. To know if it has been cast, we need the |
I don't think it's necessary to separate the two because I think it's the same error. |
@t-kameyama please follow what @BraisGabin already suggested. |
|
@t-kameyama thanks for the rule! |
|
@t-kameyama please refactor this PR so we can merge it :). And thanks again for this great contribution! |
|
PTAL |
Fixes #2901