-
-
Notifications
You must be signed in to change notification settings - Fork 768
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
Match extension functions #4459
Conversation
Codecov Report
@@ Coverage Diff @@
## main #4459 +/- ##
=========================================
Coverage 84.13% 84.13%
Complexity 3313 3313
=========================================
Files 477 477
Lines 10922 10924 +2
Branches 2030 2030
=========================================
+ Hits 9189 9191 +2
Misses 700 700
Partials 1033 1033
Continue to review full report at Codecov.
|
I have no idea what's going on here: https://github.com/detekt/detekt/runs/4726908840?check_suite_focus=true#step:4:499 I didn't change that class at all. Well, I did change it, but one year ago: 04/01/2021 |
f73771a
to
5d8588f
Compare
@@ -48,7 +48,9 @@ class ForbiddenMethodCall(config: Config = Config.empty) : Rule(config) { | |||
"Methods can be defined without full signature (i.e. `java.time.LocalDate.now`) which will report " + | |||
"calls of all methods with this name or with full signature " + | |||
"(i.e. `java.time.LocalDate(java.time.Clock)`) which would report only call " + | |||
"with this concrete signature." | |||
"with this concrete signature. If you want to forbid an extension function like" + | |||
"`fun String.hello(a: Int)` you should add the receiver parameter as the first parameter like this: " + |
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 still feels like a stop gap solution to me. When we disable type resolution, and we only want to forbid extension methods like String.hello(a: Int)
, we would actually report both extension function String.hello(a: Int)
and normal function hello(s: String, a: Int)
.
Additionally, detekt should be compatible with other kotlin platforms.
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 still feels like a stop gap solution to me.
Agree, I can't think of any other way to fix this without adding breaking changes to the syntax. And, even adding breaking changes, I can only think of more complex syntax, I can't think on any elegant way to fix this. Any idea in this regard is more than welcome.
When we disable type resolution, and we only want to forbid extension methods like
String.hello(a: Int)
, we would actually report both extension functionString.hello(a: Int)
and normal functionhello(s: String, a: Int)
.
This rule (and feature in general) only works with Type Solving so this is not a real issue.
Additionally, detekt should be compatible with other kotlin platforms.
Agree. Right now it's noy an issue because we only support type solving for JVM. But in the future maybe in js you could have both functions and that would be a problem.
The biggest issue to me is that it's not intuitive for a user how to use this feature. And I think that it should. But as I said before to handle this case properly we need to add a breaking change in the syntax. And, even then, I don't know how to define the function signature in an easy and elegant way.
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.
The biggest issue to me is that it's not intuitive for a user how to use this feature.
I think that's the major point here. TR being a requirement is not a problem, as if you want to use an advanced capability, you need to opt-in to TR.
But in the future maybe in js you could have both functions and that would be a problem.
It's already a concern, as the compiler plugin runs with TR on all the platforms.
But as I said before to handle this case properly we need to add a breaking change in the syntax. And, even then, I don't know how to define the function signature in an easy and elegant way.
What would be your suggested way?
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.
What would be your suggested way?
The toString
of a CallableDescriptor.original
of this function:
package org.example
class A<T> {
fun <R> String.hello(a: T -> R): T {
TODO()
}
}
is
public final fun <R> kotlin.String.hello(a: (T) -> R): T defined in org.example.A[SimpleFunctionDescriptorImpl@5b4f828d]
If I simplify that I get (the output of the function is not relevant for the signature):
<R> kotlin.String.hello((T) -> R) defined in org.example.A
T
is not clear where it was defined so I would do something like this:
<R> kotlin.String.hello((T) -> R) defined in org.example.A<T>
Problems:
- I don't like
defined in
but I don't know how to simplify it. - Create a parser for this can be difficult. And create proper error messages to explain to the user what they write wrong.
To compare, if we merge this PR (and the next one) the signature for that function would be:
org.example.A.hello(kotlin.String, (T) -> R)
Other option is to use the syntax of apiDump
:
public final class io/github/detekt/test/utils/ResourcesKt {
public static final fun readResourceContent (Ljava/lang/String;)Ljava/lang/String;
public static final fun resource (Ljava/lang/String;)Ljava/net/URI;
public static final fun resourceAsPath (Ljava/lang/String;)Ljava/nio/file/Path;
public static final fun resourceUrl (Ljava/lang/String;)Ljava/net/URL;
}
But I really don't like this one.
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.
Other option is to use the syntax of
apiDump
:
I was actually thinking about something like that. I know that is not super immediate to grasp. But at the same time it comes from the bytecode representation of a method, and it's sort of "consistent" with the rest of the tools in the ecosystem
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.
In that one the issues raised by @chao2zhang applies too:
- It's not easy to write
- Only works for JVM (js doesn't need a
ResourcesKt
for the top level functions) - An extension functions has it's receiver as the first parameter (that's what I'm doing here)
I agree that using something standard would be great but I don't think that syntax would work for us. And I don't know of any other standard one...
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.
How can we unblock this? To me we should think if this change improves of make worst this rule. I think that it's better than don't support extension functions. An example would like to forbid the usage of androidx.activity.compose.setContent
in favor of my our own setContent
and right now it's impossible and #4448 is other reason.
We can open an issue to talk about which should be the function signature syntax that we should use and then we can implement it. And once we take a decission we can implement it.
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.
Yup I'm in for merging this. I think having the extension function receiver modelled as first parameter is a good compromise and aligns to the approach taken by other tools as well.
5d8588f
to
dc617af
Compare
dc617af
to
6a9f0ca
Compare
Can we take a decission here? |
Would this work across all Kotlin Multiplatform? I would vote for merge if this is not particularly tailored to JVM |
Yes, it should work for all of them. |
☝️ That's my latest opinion. I'm in for merging this |
"fun foo(a: String), true", | ||
"fun Int.foo(), false", | ||
"fun String.foo(a: Int), false", | ||
"'fun foo(a: String, ba: Int)', false", |
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.
Are those single quotes around fun foo(a: String, ba: Int)
required?
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.
Yes they are. This string is parsed as a csv. And this function has two parameters so it needs a ,
. So, I need the single quotes.
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 could use a different separator though (Random guy yelling from the side line) 😉
* Add missing tests * Match extension functions
More context here: #4448 (comment) I'm implementing the no breaking option. It isn't nice but it works.
Related with #4448 but this PR doesn't fix the issue yet.