-
-
Notifications
You must be signed in to change notification settings - Fork 352
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
feat: Add CtTry.addCatcherAt
#4954
Changes from 4 commits
b7492a6
3109fa1
8bc0965
44f4d89
0510d3e
3b7bd25
1401c80
0a4c803
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -47,6 +47,19 @@ public interface CtTry extends CtStatement, TemplateParameter<Void>, CtBodyHolde | |||||
@PropertySetter(role = CATCH) | ||||||
<T extends CtTry> T addCatcher(CtCatch catcher); | ||||||
|
||||||
/** | ||||||
* Adds a catch block at the specified position in the <code>try</code> statement. | ||||||
* Behaves similarly to {@link java.util.List#add(int, Object)}. | ||||||
* | ||||||
* @param <T> the type of the try statement | ||||||
* @param position the position at which the <code>catcher</code> is to be inserted | ||||||
* @param catcher the catch statement to be inserted | ||||||
* @return this try statement | ||||||
* @throws IndexOutOfBoundsException if the position is out of range (position < 0 || position > number of catchers) | ||||||
*/ | ||||||
@PropertySetter(role = CATCH) | ||||||
<T extends CtTry> T addCatcherAt(int position, CtCatch catcher); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This is a design flaw that we need to, well, not do anymore :). I haven't been around for long enough to know why this is so prevalent in Spoon, but it's a terrible misuse of Java's generics. To illustrate why a return type like this is so problematic, consider the following code: CtTry tryStatement = new CtTryImpl();
CtCatch catcher = new CtCatchImpl();
CtTryWithResource tryWithResource = tryStatement.addCatcherAt(0, catcher); This will compile just fine without even a warning, but result in the following runtime error:
That's incredibly confusing as the calling code hasn't performed any casts, much less any unsafe casts. One could argue that we should keep using this pattern for the interface to be consistent, but this pattern is so incredibly problematic that I think breaking consistency is a small price to pay for not making this problem any larger than it already is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is indeed confusing, but why does it throw the class cast exception? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, but the actual dynamic type of the I wrote a really elaborate explanation for this, and then the page refreshed and I lost it :D. I'll give it a go again tomorrow, but the very short explanation is that the problems occur with the unchecked cast in the return statement (i.e. The actual cast here occurs when we assign to the variable with type I'll try to circle back around to the rather nasty ramifications this stuff can have: a confusing I spent way too much time on writing (and losing) the explanation here so I'll have to get back to the actual PR at a later time :) |
||||||
|
||||||
/** | ||||||
* Removes a catch block. | ||||||
*/ | ||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -64,6 +64,12 @@ public <T extends CtTry> T setCatchers(List<CtCatch> catchers) { | |||||||||||||||
|
||||||||||||||||
@Override | ||||||||||||||||
public <T extends CtTry> T addCatcher(CtCatch catcher) { | ||||||||||||||||
addCatcherAt(catchers.size(), catcher); | ||||||||||||||||
return (T) this; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
@Override | ||||||||||||||||
public <T extends CtTry> T addCatcherAt(int position, CtCatch catcher) { | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Same comment as on the interface. |
||||||||||||||||
if (catcher == null) { | ||||||||||||||||
return (T) this; | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💬 2 similar findings have been found in this PR unchecked: unchecked cast 🔎 Expand here to view all instances of this finding
Visit the Lift Web Console to find more details in your report. ℹ️ Learn about @sonatype-lift commandsYou can reply with the following commands. For example, reply with @sonatype-lift ignoreall to leave out all findings.
Note: When talking to LiftBot, you need to refresh the page to see its response. Was this a good recommendation? |
||||||||||||||||
} | ||||||||||||||||
|
@@ -72,7 +78,7 @@ public <T extends CtTry> T addCatcher(CtCatch catcher) { | |||||||||||||||
} | ||||||||||||||||
catcher.setParent(this); | ||||||||||||||||
getFactory().getEnvironment().getModelChangeListener().onListAdd(this, CATCH, this.catchers, catcher); | ||||||||||||||||
catchers.add(catcher); | ||||||||||||||||
catchers.add(position, catcher); | ||||||||||||||||
return (T) this; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -26,6 +26,7 @@ | |||||||||||||||||||||||||||||||||||||||
import java.util.Set; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
import org.apache.commons.lang3.StringUtils; | ||||||||||||||||||||||||||||||||||||||||
import org.junit.jupiter.api.Nested; | ||||||||||||||||||||||||||||||||||||||||
import org.junit.jupiter.api.Test; | ||||||||||||||||||||||||||||||||||||||||
import org.junit.jupiter.api.extension.ExtendWith; | ||||||||||||||||||||||||||||||||||||||||
import spoon.Launcher; | ||||||||||||||||||||||||||||||||||||||||
|
@@ -36,7 +37,6 @@ | |||||||||||||||||||||||||||||||||||||||
import spoon.reflect.code.CtResource; | ||||||||||||||||||||||||||||||||||||||||
import spoon.reflect.code.CtTry; | ||||||||||||||||||||||||||||||||||||||||
import spoon.reflect.code.CtTryWithResource; | ||||||||||||||||||||||||||||||||||||||||
import spoon.reflect.code.CtVariableRead; | ||||||||||||||||||||||||||||||||||||||||
import spoon.reflect.declaration.CtClass; | ||||||||||||||||||||||||||||||||||||||||
import spoon.reflect.declaration.CtMethod; | ||||||||||||||||||||||||||||||||||||||||
import spoon.reflect.declaration.CtVariable; | ||||||||||||||||||||||||||||||||||||||||
|
@@ -56,11 +56,13 @@ | |||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
import static org.hamcrest.CoreMatchers.equalTo; | ||||||||||||||||||||||||||||||||||||||||
import static org.hamcrest.MatcherAssert.assertThat; | ||||||||||||||||||||||||||||||||||||||||
import static org.hamcrest.collection.IsIterableContainingInOrder.contains; | ||||||||||||||||||||||||||||||||||||||||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||||||||||||||||||||||||||||||||||||||||
import static org.junit.jupiter.api.Assertions.assertFalse; | ||||||||||||||||||||||||||||||||||||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals; | ||||||||||||||||||||||||||||||||||||||||
import static org.junit.jupiter.api.Assertions.assertNotNull; | ||||||||||||||||||||||||||||||||||||||||
import static org.junit.jupiter.api.Assertions.assertNull; | ||||||||||||||||||||||||||||||||||||||||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||||||||||||||||||||||||||||||||||||||||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||||||||||||||||||||||||||||||||||||||||
import static org.junit.jupiter.api.Assertions.fail; | ||||||||||||||||||||||||||||||||||||||||
import static spoon.testing.utils.ModelUtils.build; | ||||||||||||||||||||||||||||||||||||||||
|
@@ -442,4 +444,60 @@ public void testTryWithVariableAsResource() { | |||||||||||||||||||||||||||||||||||||||
tryStmt.removeResource(ctResource); | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
@Nested | ||||||||||||||||||||||||||||||||||||||||
class AddCatcherAt { | ||||||||||||||||||||||||||||||||||||||||
@Test | ||||||||||||||||||||||||||||||||||||||||
void addsCatcherAtTheSpecifiedPosition() { | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: This test really tests three separate cases in one go:
Given that this functionality is neither complicated nor critical to Spoon, that's probably fine. But it always bears thinking about how much is tested in any single test. The fact that we never insert at the end of a non-empty list also isn't a big deal considering what the implementation looks like. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Right. Ideally, we should test only one case per unit test. I will keep this in mind. We can skip refactoring for this feature because, like you said, it is not that critical and is rather a very simple feature.
Tests of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Precisely, "considering what the implementation looks like" :). White box testing has merits in that we can omit redundant testing, but it has drawbacks to match (e.g. creating tests based on the implementation rather than the specification). |
||||||||||||||||||||||||||||||||||||||||
// contract: the catcher should be added at the specified position | ||||||||||||||||||||||||||||||||||||||||
// arrange | ||||||||||||||||||||||||||||||||||||||||
Factory factory = createFactory(); | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
CtTry tryStatement = factory.createTry(); | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
CtCatch first = factory.createCatch(); | ||||||||||||||||||||||||||||||||||||||||
first.setParameter(factory | ||||||||||||||||||||||||||||||||||||||||
.createCatchVariable() | ||||||||||||||||||||||||||||||||||||||||
.setType(factory.Type().createReference(IOException.class))); | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
CtCatch second = factory.createCatch(); | ||||||||||||||||||||||||||||||||||||||||
second.setParameter(factory | ||||||||||||||||||||||||||||||||||||||||
.createCatchVariable() | ||||||||||||||||||||||||||||||||||||||||
.setMultiTypes(List.of( | ||||||||||||||||||||||||||||||||||||||||
factory.Type().createReference(ExceptionInInitializerError.class), | ||||||||||||||||||||||||||||||||||||||||
factory.Type().createReference(NoSuchFieldException.class)))); | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
CtCatch third = factory.createCatch(); | ||||||||||||||||||||||||||||||||||||||||
third.setParameter(factory | ||||||||||||||||||||||||||||||||||||||||
.createCatchVariable() | ||||||||||||||||||||||||||||||||||||||||
.setType(factory.Type().createReference(NoSuchMethodException.class))); | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is kind of a mouthful to read, and there is as far as I can tell unnecessary complexity in having a catcher that catches multiple types (I can't see how this influences insertion?). I'd suggest something like this:
Suggested change
With a helper like so: private <T> CtCatch createCatch(Factory factory, Class<T> typeToCatch) {
CtTypeReference<T> typeReference = factory.Type().createReference(typeToCatch);
return factory.createCatch().setParameter(
factory.createCatchVariable().setType(typeReference)
);
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Any reason why we pass the same instance of |
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
// act | ||||||||||||||||||||||||||||||||||||||||
tryStatement.addCatcherAt(0, third); | ||||||||||||||||||||||||||||||||||||||||
tryStatement.addCatcherAt(0, first); | ||||||||||||||||||||||||||||||||||||||||
tryStatement.addCatcherAt(1, second); | ||||||||||||||||||||||||||||||||||||||||
algomaster99 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
// assert | ||||||||||||||||||||||||||||||||||||||||
assertThat(tryStatement.getCatchers(), contains(first, second, third)); | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
@Test | ||||||||||||||||||||||||||||||||||||||||
void throwsOutOfBoundsException_whenPositionIsOutOfBounds() { | ||||||||||||||||||||||||||||||||||||||||
// contract: `addCatcherAt` should throw an out-of-bounds exception when the specified position is out of | ||||||||||||||||||||||||||||||||||||||||
// bounds of the catcher collection | ||||||||||||||||||||||||||||||||||||||||
SirYwell marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
// arrange | ||||||||||||||||||||||||||||||||||||||||
Factory factory = createFactory(); | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
CtTry tryStatement = factory.createTry(); | ||||||||||||||||||||||||||||||||||||||||
CtCatch catcherAtWrongPosition = factory.createCatch(); | ||||||||||||||||||||||||||||||||||||||||
catcherAtWrongPosition.setParameter(factory | ||||||||||||||||||||||||||||||||||||||||
.createCatchVariable() | ||||||||||||||||||||||||||||||||||||||||
.setType(factory.Type().createReference(Exception.class))); | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also use the proposed helper here :) |
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
// act & assert | ||||||||||||||||||||||||||||||||||||||||
assertThrows(IndexOutOfBoundsException.class, | ||||||||||||||||||||||||||||||||||||||||
() -> tryStatement.addCatcherAt(2, catcherAtWrongPosition)); | ||||||||||||||||||||||||||||||||||||||||
algomaster99 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
} |
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 am using
position
to refer toindex
in thecatchers
collection because it has been used for all analogous methods. Seespoon/src/main/java/spoon/reflect/code/CtAbstractSwitch.java
Line 72 in 76ffd33
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 it's a fine enough word. "Index" is rather data structure specific, position feels more implementation agnostic to me.