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

review: feat(sniper): detailed tree of source fragments of compilation unit #2283

Merged
merged 16 commits into from
Sep 11, 2018

Conversation

pvojtechovsky
Copy link
Collaborator

@pvojtechovsky pvojtechovsky commented Jul 26, 2018

this PR introduces new types:

  • SourceFragment
  • ElementSourceFragment implements SourceFragment
  • TokenSourceFragment implements SourceFragment
  • CollectionSourceFragment implements SourceFragment

ElementSourceFragment is a wrapper around CtElement and it's SourcePosition. It also builds hierarchy of ElementSourceFragments, which mirrors hierarchy of fragments of source code of compilation unit.

It provides 3 views at tree of source fragments

  1. Tree of ElementSourceFragments. It consists of fragments defined by SourcePosition.start/end of CtElements only. It is basic structure, which must be build after Spoon model is created and before it is modified.
  2. Each ElementSourceFragment can be optionally expanded to detailed tree which contains TokenSourceFragments next to child ElementSourceFragments. See ElementSourceFragment#getChildrenFragments().
  3. Each detailed tree can be reorganized into so called grouped tree, which contains same information like detailed tree, but additionally SourceFragments, which belongs to same parent CtRole are grouped together and represented by CollectionSourceFragment

Notes:

  • The detailed tree consists of small parser tokens, which are equal to tokens produced by DJPP and TokenWriter.
  • The detailed tree is based on simple SourcePositionImpl only. It doesn't need any details provided e.g. by DeclarationSourcePosition
  • The detailed tree uses simple parser to detect spaces, separators, keywords and identifiers.

@pvojtechovsky pvojtechovsky force-pushed the feaSourceFragment branch 2 times, most recently from 06f1446 to ff24212 Compare August 21, 2018 17:51
@pvojtechovsky pvojtechovsky changed the title WIP: feat: detailed tree of source fragments of compilation unit review: feat: detailed tree of source fragments of compilation unit Aug 21, 2018
@Override
public ElementSourceFragment getRootSourceFragment() {
if (rootFragment == null) {
String originSourceCode = getOriginalSourceCode();
Copy link
Collaborator

Choose a reason for hiding this comment

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

getOriginalSourceCode() might return null if getFile() returns null. You should check the result to avoid NPE here.

for (CtImport imprt : getImports()) {
rootFragment.addChild(new ElementSourceFragment(imprt, null /*TODO role for import of CU*/));
}
for (CtType<?> ctType : declaredTypes) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The CU can also be used for package declaration or module declaration: in those cases, you don't create the tree?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I forgot that cases. As fast solution: I will check them and throw SpoonException(...not supported...). Later we can check if we can create the tree for them too - I have no idea yet if it is easily possible or needs more effort.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks ok for me. Maybe it worth opening an issue to avoid forgetting it.

@@ -474,6 +477,39 @@ public void scan(CtRole role, CtElement element) {
});
}

@Test
public void testSourcePositionTreeIsCorrectlyOrdered() {
List<CtType> types = rootPackage.filterChildren(new TypeFilter<>(CtType.class)).filterChildren((CtType t) -> t.isTopLevel()).list();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you specify the contract?

@spoon-bot
Copy link
Collaborator

API changes: 2 (Detected by Revapi)

Old API: fr.inria.gforge.spoon:spoon-core:jar:7.1.0-20180827.060031-119 / New API: fr.inria.gforge.spoon:spoon-core:jar:7.1.0-SNAPSHOT

Method was added to an interface.
Old none
New method CompilationUnit#getRootSourceFragment()
Breaking binary: non_breaking
Method was added to an interface.
Old none
New method CompilationUnit#getSourceFragment(SourcePositionHolder)
Breaking binary: non_breaking

@monperrus
Copy link
Collaborator

It looks interesting. What's the final goal of this new feature?

What do you mean by "ElementSourceFragment is a wrapper around CtElement and its SourcePosition"?

@pvojtechovsky
Copy link
Collaborator Author

It looks interesting. What's the final goal of this new feature?

It is core feature need by #1927 - sniper mode.

What do you mean by "ElementSourceFragment is a wrapper around CtElement and its SourcePosition"?

yes,

  1. it connects element and source position
  2. it knows children and sibling fragments, so it builds tree of source fragments

It is not enough to link element only and to use CtElement#getPosition, because

  • the position might be changed during refactoring and we need origin elements and origin source positions which maps to origin sources (before refactoring)

@monperrus
Copy link
Collaborator

It is core feature need by #1927 - sniper mode.

Excellent!

we need origin elements and origin source positions which maps to origin sources (before refactoring)

Do you mean something like CtElement#getOriginalPosition and CtElement#getOriginalElement?

@monperrus monperrus changed the title review: feat: detailed tree of source fragments of compilation unit review: feat(sniper): detailed tree of source fragments of compilation unit Aug 27, 2018
@pvojtechovsky
Copy link
Collaborator Author

we need origin elements and origin source positions which maps to origin sources (before refactoring)

Do you mean something like CtElement#getOriginalPosition and CtElement#getOriginalElement?

I would say no. But may be I do not understand your idea.

The concept of this PR and core feature for sniper mode is:

  • The compilation unit has origin source code
  • The compilation unit has tree of SourceFragments, which mirrors the tokens from origin source code
  • There are different kinds of SourceFragments: ElementSourceFragment, TokenSourceFragment, ...
  • ElementSourceFragment represents a fragment which mirrors the CtElement
  • TokenSourceFragment represents a fragment of primitive tokens, like separators, newlines, etc. It is not mapped to any child CtElement.

So from this concept point of view there is relation from ElementSourceFragment to the CtElement, which was made during building of origin Spoon model. Opposite relation is not needed.

When the spoon model is changed later (some CtElements are cloned, added, modified, moved, removed, ... ) then these new CtElements doesn't have source position, etc. So it makes no sense to ask these elements for origin position or origin element.

@monperrus
Copy link
Collaborator

  • The compilation unit has origin source code

Do you mean getRootSourceFragment?

  • The compilation unit has tree of SourceFragments, which mirrors the tokens from origin source code

I would propose to rename ElementSourceFragment to CtElementMirror. WDYT?

  • ElementSourceFragment represents a fragment which mirrors the CtElement

I expect a field CtElement mirroredElem there. But there is no such field. What's the reason?

@pvojtechovsky
Copy link
Collaborator Author

The compilation unit has origin source code

I meant String getOriginSourceCode() method.

I would propose to rename ElementSourceFragment to CtElementMirror. WDYT?
I expect a field CtElement mirroredElem there. But there is no such field. What's the reason?

It looks like you look at the concept/feature from wrong direction...
The primary concept of this feature is to split source code (represented as one long String) to the small Strings which are semantically equal to TokenWriter tokens.
Then these tokens are organized into tree structure, which is represented by SourceFragments.
Then some source fragments can be mapped to CtElements (see ElementSourceFragment).

There is no reason why EVERY CtElement should know it's ElementSourceFragment, but there is algoritm in CompilationUnit which can search and return ElementSourceFragment for CtElement - if there exist such ElementSourceFragment...

@monperrus Is it more clear now?

@monperrus
Copy link
Collaborator

It's not clear but it's better. It looks interesting.

As we've done in the past, are you OK if I propose some renaming and refactoring by pushing in your branch?

@pvojtechovsky
Copy link
Collaborator Author

pushing in your branch?

Yes sure, I am ok with that. But it would be better for me if you would do the refactoring directly in #1927 - branch refDJPP. It contains code which uses classes from this PR. After you refactor it, I can rebase interactive your changes by way the part will become part of feaSourceFragment branch and another part will become part of refDJPP.

Otherwise you will refactor only feaSourceFragment and I will have to then fix compilation problems in refDJPP manually without refactoring tool :-(

WDYT?

By the way I am open to discuss merging of new SourceFragment and old SourcePosition somehow - but before we can discuss it it needs your understanding of SourceFragment concept and implementation of #1927.

@monperrus
Copy link
Collaborator

In #2419 this the same code, with a refactoring:

  • removed getRootSourceFragment and getSourceFragment from CompilationUnit
  • added getOriginalSourceFragment in SourcePositionHolder

Does it make sense to you?

@pvojtechovsky
Copy link
Collaborator Author

I like it a lot!

I just do not know how it will behave it this method is called on the brand new CtElement, which was made by cloning or Factory#createXxx call. We should make a test and define some expected behavior.

@monperrus
Copy link
Collaborator

monperrus commented Aug 30, 2018 via email

@pvojtechovsky
Copy link
Collaborator Author

Just added a test with contract "on a new element, getOriginalSourceFragment returns the actual
element toString"

May be it would be better to return some constant SourceFragment NO_SOURCE_FRAGMENT ... because

  • there is no original source fragment in new CtElement
  • the returned source fragment made by toString, doesn't conform to source fragment concept: source fragment consists of tree of smalled source fragments which represents TokenWriter tokens.

WDYT?

@monperrus
Copy link
Collaborator

May be it would be better to return some constant SourceFragment NO_SOURCE_FRAGMENT ... because

You're right, it would be better.

@monperrus
Copy link
Collaborator

do you continue based on the design we discussed in #2419?

@pvojtechovsky
Copy link
Collaborator Author

OK, I can integrate these change into main PR today or tomorrow evening.

@monperrus
Copy link
Collaborator

monperrus commented Sep 5, 2018 via email

@pvojtechovsky
Copy link
Collaborator Author

I do not understand why CI failed here. Is it problem of CI or this PR?

I am finished with my changes here.

@monperrus
Copy link
Collaborator

Thanks Pavel.

I've put the implementation classes in an internal package.

Let me think about it and add some more documentation.

@monperrus
Copy link
Collaborator

and I want to have a look at the tests, which look super interesting!!

@pvojtechovsky
Copy link
Collaborator Author

Why this change?

ElementSourceFragment getOriginalSourceFragment() {

changed to

SourceFragment getOriginalSourceFragment()

It can never return SourceFragment. It is always ElementSourceFragment. ElementSourceFragment is not just implementation of SourceFragment interface. It means a special Source fragment which is linked to an CtElement. Other SourceFragments might be for example linked to a token.

@monperrus
Copy link
Collaborator

monperrus commented Sep 7, 2018 via email

@pvojtechovsky
Copy link
Collaborator Author

One solution is to extract an interface out of ElementSourceFragment

I understand, but If I remember well, you already agreed that in some cases Spoon uses interfaces too much. I do not think that it is helpful to have interface ElementSourceFragment and exactly one class ElementSourceFragmentImpl ... nobody will ever write different implementation for ElementSourceFragment
WDYT? ;-)

@monperrus
Copy link
Collaborator

monperrus commented Sep 10, 2018 via email

@monperrus
Copy link
Collaborator

Pavel, to your opinion, is ElementSourceFragment meant to be used by clients? or is it more an internal class for us?

For clients, what about a simple method in the interface:

String getOriginalSourceFragment()

WDYT?

@pvojtechovsky
Copy link
Collaborator Author

String getOriginalSourceFragment()

short answer: No

long answer: ElementSourceFragment getOriginalSourceFragment() returns root ElementSourceFragment of current element. That root ElementSourceFragment contains subtree of all child fragments, which is the core feature needed to be able to print sources of element using origin sources as much as possible -> sniper mode.

is ElementSourceFragment meant to be used by clients?

I would say, normally clients doesn't need that, same like they doesn't need SourcePosition. But these clients who wants to play with origin sources, will highly appreciate that detailed source tree.

@monperrus
Copy link
Collaborator

then, I moved SourceFragment to package "internal" and we're ready to merge.

Copy link
Collaborator

@monperrus monperrus left a comment

Choose a reason for hiding this comment

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

Thanks Pavel, for this new advanced feature.

@monperrus monperrus merged commit 27f50a2 into INRIA:master Sep 11, 2018
@pvojtechovsky pvojtechovsky deleted the feaSourceFragment branch September 12, 2018 05:06
@monperrus monperrus mentioned this pull request Sep 20, 2018
4 tasks
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.

4 participants