-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
[java] Multiple improvements to JavaQualifiedName #895
Conversation
Remove getQualifiedName's copypasta.
The unnamed package is not represented as a lone full stop anymore. Rename QualifiedNameTest into JavaQualifiedNameTest
* Simplify the API of JavaQualifiedName * The name of an anonymous class is for now only available via JavaQualifiedName factories, not from the nodes
Use an immutable list to avoid array copies everywhere
Use possessive quantifiers to make it fail faster
Add MethodLike superclass to ASTMethodDecl, ASTConstructorDecl, ASTLambdaExpr
Qualified names now use structural comparison to determine equality. Using toString hid away some bugs. We still use toString to shortcut the comparison, since it's cached.
Doesn't support method overriden from generic supertype, anonymous enum constants (due to a bug in typeres. The fix is somewhere in my branches, after pmd#895)
Doesn't support method overriden from generic supertype, anonymous enum constants (due to a bug in typeres. The fix is somewhere in my branches, after pmd#895)
This is part of a bigger changeset, which aims to implement #864. The next PR in line is an implementation of the qualified name resolving visitor discussed in that issue, which significantly reduces the complexity of the qualified name resolution code, and simplifies some parts of ClassTypeResolver. The changeset as a whole fixes a lot of type resolution bugs and clarifies its code. I think this PR should be scheduled for 6.2.0 to move forward. |
@oowekyala great! I'll schedule some time for this over my weekend. It's a large PR, but I think I can make it. |
@jsotuyod Great, thanks :) |
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 changeset introduces too many breaking API changes to the AST... as it stands, it can't be part of 6.2.0. I think we can work it out in 2 stages, where we incrementaly change things in 6.2.0 and then in 7.0.0 remove everything made deprecated / redundant, but it will take a little more effort.
Otherwise, we would have to wait for 7.0.0 to be able to see this productive...
@@ -7,9 +7,8 @@ | |||
|
|||
import java.util.List; | |||
|
|||
public class ASTAnnotationTypeDeclaration extends AbstractJavaAccessTypeNode implements ASTAnyTypeDeclaration { | |||
public class ASTAnnotationTypeDeclaration extends ASTAnyTypeDeclaration { |
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 is a breaking API change. Do we really need to do it in 6.2.0? Can't we just deprecate the methods?
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.
Which methods?
* | ||
* @author Clément Fournier | ||
*/ | ||
public interface ASTAnyTypeDeclaration extends JavaQualifiableNode, AccessNode, JavaNode { | ||
public abstract class ASTAnyTypeDeclaration extends AbstractJavaAccessTypeNode implements JavaQualifiableNode, AccessNode, JavaNode { |
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 change from interface to abstract class is also a breaking API change
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 had not thought about that. We could very much keep the abstract class and interface separate, which wouldn't be API breaking. We'd just insert an abstract class in the hierarchy.
@@ -10,13 +10,12 @@ | |||
import net.sourceforge.pmd.lang.ast.Node; | |||
|
|||
|
|||
public class ASTClassOrInterfaceDeclaration extends AbstractJavaAccessTypeNode implements ASTAnyTypeDeclaration { | |||
public class ASTClassOrInterfaceDeclaration extends ASTAnyTypeDeclaration { |
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.
ditto
|
||
public class ASTConstructorDeclaration extends AbstractJavaAccessNode implements ASTMethodOrConstructorDeclaration { | ||
public class ASTConstructorDeclaration extends ASTMethodOrConstructorDeclaration { |
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.
ditto
@@ -7,9 +7,8 @@ | |||
|
|||
import java.util.List; | |||
|
|||
public class ASTEnumDeclaration extends AbstractJavaAccessTypeNode implements ASTAnyTypeDeclaration { | |||
public class ASTEnumDeclaration extends ASTAnyTypeDeclaration { |
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.
ditto
* @author Clément Fournier | ||
* @since 6.1.0 | ||
*/ | ||
public interface ImmutableList<E> extends Iterable<E> { |
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.
why do we need our own inmutable list implementation? What's wrong with Collections.unmodifiableList
?
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.
Collections.unmodifiableList
isn't persistent, and doesn't have a free prepend or append operation. I feel like an immutable list like what's found in functional languages is perfect for the problem, and especially for the visitor (next PR). It's good for clarity and conciseness of code, clarity of interface, and performance too. I implemented what I was missing.
If using this interface instead of List for the return type of a few methods doesn't bother you in itself, then the factories are private so the only dependable code we introduce is the interface, which is in itself very thin. Otherwise, well we could use util.List, but I think it would feel like a workaround...
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 just very cautious about including (and maintaining!) ~600 LoCs for a basic data structure for which Java has several implementations and decorators.... Even if you want a smaller API just allowing prepends, does it earn us anything at all to manually implement the linked list over just using LinkedList
?
This implementation has constant-time prepend, size, head and tail
LinkedList
:
- constant-time
addFirst()
- constant-time
size()
- constant-time
getFirst()
- constant-time
getLast()
Most other operations are linear-time, but caching helps reduce the overhead of some common operations (reverse)
Equally applicable over a LinkedList
Allows structural sharing of the tail, which makes many operations constant- or zero-memory cost.
LinkedList
:
-
subList()
provides a "view" of the original list, effectively sharing whatever part of the list you want
Even if we keep the ImmutableList<E>
interface as API, most of the current implementation is just a LinkedList
when a thin wrapper over LinkedList
suffices; so I'd rather just use it than reinvent the wheel
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 interface of a LinkedList is very different from the interface of that list: void addFirst(E)
vs ImmutableList<E> prepend(E)
. A LinkedList can add an element to a list in o(1), while an immutable list can create a new list with one more element in o(1) (time and space). We don't want to add elements fast, we want to create a new, bigger collection fast, and never add elements. This is something you only get with o(n) time and space in java, because you have to copy the old list each time
Besides subList cannot be used for proper structural sharing. You could use it for a tail
operation but not for a prepend
. From the subList doc:
The semantics of the list returned by this method become undefined if the backing list (i.e., this list) is structurally modified in any way other than via the returned list. (Structural modifications are those that change the size of this list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.)
I can't see a way to wrap a LinkedList to make it conform to that interface, without creating a full new LinkedList at each prepend operation, which is exactly what I was trying to avoid.
I understand the concern about not reimplementing the wheel because it's hard to maintain, but the Java standard lib lacks immutable and persistent collections, which is a shame. Alternatively, I found a light 3rd party lib that implements some basic ones, and is very compatible with the standard Collections framework. It's less than 20 classes, and I think it could be useful in other places -- some algorithms are better expressed with them. I'd much rather use that than keep the current full implementation -- i just didn't bother to search
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'd rather not add a dependency for a single class.
I'm still hesitant, specially given you seems to be using little of that behavior on this PR, but I'll give you benefit of the doubt to see what's in follow up PRs. I hope you can convince me, I'd hate to see us roll it back in 7.0.0
However, I still strongly believe this class should not be part of this package.
JavaQualifiableNode, | ||
AccessNode, | ||
JavaNode { | ||
public abstract class ASTMethodOrConstructorDeclaration extends MethodLike implements SignedNode<ASTMethodOrConstructorDeclaration> { |
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.
ditto
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html | ||
*/ | ||
|
||
package net.sourceforge.pmd.lang.java.ast; |
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 should probably not be part of this package
* @author Clément Fournier | ||
* @since 6.1.0 | ||
*/ | ||
public abstract class MethodLike extends AbstractJavaAccessNode implements JavaQualifiableNode, JavaNode { |
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.
why is this the only class on the AST that is not named a Node
?
@@ -4,379 +4,156 @@ | |||
|
|||
package net.sourceforge.pmd.lang.java.ast; |
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 should probably not be part of this package
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.
Moving it out would be API breaking
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.
True, I've added it to #881. I'd avoid having the factory on this package non the less to avoid that breaking change in 7.0.0
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 next PR moves ~80% of the factory out into the visitor. If you're ok with it, I'd rather move it out of the package then, otherwise I'd have some merge conflicts to resolve ^^
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.
If the next PR doesn't make it into 6.2.0 we would be in the very difficult position of either having to roll this back before the release, or deprecate it in 6.3.0. Not the happiest path in either case... I'm trusting you on this one, but let's be careful.
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'll open the next PR asap
Removes a breaking API change
Remove one breaking API change
Spotted using japicmp
Checked with japicmp
@jsotuyod I used Japicmp to test binary compatibility with the latest released version. This version of the PR introduces only minute binary compatibility-breaking changes, namely, I didn't bridge the ***! MODIFIED CLASS: PUBLIC net.sourceforge.pmd.lang.java.metrics.impl.NpathMetric (not serializable)
=== CLASS FILE FORMAT VERSION: 51.0 <- 51.0
---! REMOVED METHOD: PUBLIC(-) double computeFor(net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration, net.sourceforge.pmd.lang.metrics.MetricOptions) Bridging preserves binary compatibility but compels clients to explicitly add a cast to the method argument to link to the recommended method instead of the deprecated one. Without bridging, a simple recompilation links correctly to the new API. Under those conditions, I'm not sure whether adding bridges like in 2fd5d7c is the best idea, but at least it's binary compatible now, except for those corner cases which I think should be rare enough. |
? "method" | ||
: node.getKind() == MethodLikeKind.CONSTRUCTOR | ||
? "constructor" | ||
: "lambda"; |
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.
sounds like a name for MethodLikeKind
, something that should be part of the enum itself possibly
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 think I can open another PR for this small changeset
@oowekyala I'm merging this as is, so as to not block you anymore. Please, look into the remaining comments in upcoming PRs. Sorry for the delay, the changeset resulted to be a little less straightforward than anticipated. |
Hi,
I thought that the commit history is clean enough to submit all that at once, but let me know if you want me to split the PR
Many refactorings were made in view of #864, and the PR adds support for anonymous class and lambda expression qnames
Commit b30d64d fixes #837