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

[java] Multiple improvements to JavaQualifiedName #895

Merged
merged 25 commits into from
Mar 19, 2018

Conversation

oowekyala
Copy link
Member

@oowekyala oowekyala commented Jan 31, 2018

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

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
@oowekyala oowekyala added the in:pmd-internals Affects PMD's internals label Feb 4, 2018
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.
oowekyala added a commit to oowekyala/pmd that referenced this pull request Feb 13, 2018
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)
oowekyala added a commit to oowekyala/pmd that referenced this pull request Feb 13, 2018
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)
oowekyala added a commit to oowekyala/pmd that referenced this pull request Feb 13, 2018
Doesn't support method overriden from generic supertype.
Known limitations that are supposed to be fixed shortly
are pmd#910 and anonymous enum constants (the fix for that
is somewhere in my branches, after pmd#895)
@oowekyala
Copy link
Member Author

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 oowekyala added this to the 6.2.0 milestone Mar 1, 2018
@jsotuyod
Copy link
Member

jsotuyod commented Mar 2, 2018

@oowekyala great! I'll schedule some time for this over my weekend. It's a large PR, but I think I can make it.

@oowekyala
Copy link
Member Author

@jsotuyod Great, thanks :)

@jsotuyod jsotuyod self-assigned this Mar 3, 2018
Copy link
Member

@jsotuyod jsotuyod left a 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 {
Copy link
Member

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?

Copy link
Member Author

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 {
Copy link
Member

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

Copy link
Member Author

@oowekyala oowekyala Mar 3, 2018

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 {
Copy link
Member

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 {
Copy link
Member

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 {
Copy link
Member

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> {
Copy link
Member

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?

Copy link
Member Author

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...

Copy link
Member

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

Copy link
Member Author

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

Copy link
Member

@jsotuyod jsotuyod Mar 19, 2018

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> {
Copy link
Member

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;
Copy link
Member

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 {
Copy link
Member

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;
Copy link
Member

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

Copy link
Member Author

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

Copy link
Member

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

Copy link
Member Author

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 ^^

Copy link
Member

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.

Copy link
Member Author

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

@oowekyala
Copy link
Member Author

@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 supports method of concrete operation metrics classes, like what I did in 2fd5d7c. E.g.:

***! 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.

@oowekyala oowekyala requested a review from jsotuyod March 10, 2018 18:29
? "method"
: node.getKind() == MethodLikeKind.CONSTRUCTOR
? "constructor"
: "lambda";
Copy link
Member

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

Copy link
Member Author

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

@jsotuyod
Copy link
Member

@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.

@jsotuyod jsotuyod merged commit c967e63 into pmd:master Mar 19, 2018
jsotuyod added a commit that referenced this pull request Mar 19, 2018
@oowekyala oowekyala deleted the improve-javaqualifiedname branch March 19, 2018 13:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in:pmd-internals Affects PMD's internals
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[java] CFGs of declared but not called lambdas are treated as parts of an enclosing method's CFG
2 participants