1515import com .sun .source .tree .ModifiersTree ;
1616import com .sun .source .tree .PackageTree ;
1717import com .sun .source .tree .ParameterizedTypeTree ;
18- import com .sun .source .tree .ParenthesizedTree ;
1918import com .sun .source .tree .PrimitiveTypeTree ;
2019import com .sun .source .tree .ReturnTree ;
21- import com .sun .source .tree .StatementTree ;
2220import com .sun .source .tree .ThrowTree ;
2321import com .sun .source .tree .Tree ;
2422import com .sun .source .tree .TryTree ;
2725import com .sun .source .tree .WildcardTree ;
2826import com .sun .source .util .SimpleTreeVisitor ;
2927import java .util .List ;
28+ import org .checkerframework .checker .nullness .qual .Nullable ;
3029
3130/**
3231 * A visitor that visits two javac ASTs simultaneously that almost match.
4645 * <p>To use this class, extend it, override defaultPairAction, and begin traversal by calling scan
4746 * on the two root trees.
4847 */
49- @ SuppressWarnings ("optional:method.invocation" )
5048public abstract class DoubleJavacVisitor extends SimpleTreeVisitor <Void , Tree > {
5149
5250 /** Create a DoubleJavacVisitor. */
@@ -62,7 +60,7 @@ public DoubleJavacVisitor() {}
6260 * @param tree1 the first tree in the matched pair
6361 * @param tree2 the second tree in the matched pair
6462 */
65- public abstract void defaultPairAction (Tree tree1 , Tree tree2 );
63+ protected abstract void defaultPairAction (Tree tree1 , Tree tree2 );
6664
6765 /**
6866 * The fallback visitor method used when no specific visitXxx override exists for a tree kind.
@@ -91,16 +89,13 @@ protected Void defaultAction(Tree tree1, Tree tree2) {
9189 * <p>2. Treats exactly one null as an error, because it indicates the trees are no longer
9290 * aligned.
9391 *
94- * <p>3. Normalizes wrapper nodes (such as parentheses and expression statements) so that
95- * logically corresponding constructs in the two trees align.
96- *
97- * <p>4. Verifies that the normalized trees have the same kind, then dispatches to the appropriate
98- * visit method via accept.
92+ * <p>3. Verifies that the trees have the same kind, then dispatches to the appropriate visit
93+ * method via accept.
9994 *
10095 * @param tree1 the first tree to scan, or null
10196 * @param tree2 the second tree to scan, or null
10297 */
103- public final void scan (Tree tree1 , Tree tree2 ) {
98+ public final void scan (@ Nullable Tree tree1 , @ Nullable Tree tree2 ) {
10499 if (tree1 == null && tree2 == null ) {
105100 return ;
106101 }
@@ -112,17 +107,17 @@ public final void scan(Tree tree1, Tree tree2) {
112107 this .getClass ().getCanonicalName (), tree1 , tree2 ));
113108 }
114109
115- Tree t1 = normalize ( tree1 );
116- Tree t2 = normalize ( tree2 );
117-
118- if (t1 .getKind () != t2 .getKind ()) {
110+ // If we later discover javac introduces wrappers that desynchronize traversal (e.g.
111+ // parentheses),
112+ // we can re-introduce a normalization step here.
113+ if (tree1 .getKind () != tree2 .getKind ()) {
119114 throw new Error (
120115 String .format (
121116 "%s.scan: mismatched kinds: %s vs %s" ,
122- this .getClass ().getCanonicalName (), t1 .getKind (), t2 .getKind ()));
117+ this .getClass ().getCanonicalName (), tree1 .getKind (), tree2 .getKind ()));
123118 }
124119
125- t1 .accept (this , t2 );
120+ tree1 .accept (this , tree2 );
126121 }
127122
128123 /**
@@ -135,7 +130,7 @@ public final void scan(Tree tree1, Tree tree2) {
135130 * @param tree1 the first tree, or null
136131 * @param tree2 the second tree, or null
137132 */
138- public final void scanOpt (Tree tree1 , Tree tree2 ) {
133+ public final void scanOpt (@ Nullable Tree tree1 , @ Nullable Tree tree2 ) {
139134 if (tree1 == null && tree2 == null ) {
140135 return ;
141136 }
@@ -182,58 +177,6 @@ public final void scanList(List<? extends Tree> list1, List<? extends Tree> list
182177 }
183178 }
184179
185- /**
186- * Normalizes a tree by removing wrapper nodes that should not affect structural matching.
187- *
188- * <p>javac may introduce wrapper nodes such as parenthesized expressions or expression statements
189- * depending on context. These wrappers are removed so that corresponding logical constructs in
190- * the two trees align during traversal.
191- *
192- * <p>Normalization is applied repeatedly until the tree stops changing, so nested wrappers are
193- * fully removed.
194- *
195- * @param tree a non-null tree
196- * @return a normalized tree
197- */
198- protected Tree normalize (Tree tree ) {
199- Tree t = tree ;
200- while (true ) {
201- Tree next = unwrapExpressionStatement (unwrapParentheses (t ));
202- if (next == t ) {
203- return t ;
204- }
205- t = next ;
206- }
207- }
208-
209- /**
210- * If the given tree is a parenthesized expression, returns the enclosed expression. Otherwise,
211- * returns the tree unchanged.
212- *
213- * @param tree a non-null tree
214- * @return the unwrapped tree
215- */
216- protected Tree unwrapParentheses (Tree tree ) {
217- if (tree instanceof ParenthesizedTree ) {
218- return ((ParenthesizedTree ) tree ).getExpression ();
219- }
220- return tree ;
221- }
222-
223- /**
224- * If the given tree is an expression statement, returns the underlying expression. Otherwise,
225- * returns the tree unchanged.
226- *
227- * @param tree a non-null tree
228- * @return the unwrapped tree
229- */
230- protected Tree unwrapExpressionStatement (Tree tree ) {
231- if (tree instanceof ExpressionStatementTree ) {
232- return ((ExpressionStatementTree ) tree ).getExpression ();
233- }
234- return tree ;
235- }
236-
237180 /**
238181 * Visits a compilation unit (top-level file node) and scans its main children.
239182 *
@@ -533,9 +476,7 @@ public Void visitBlock(BlockTree tree1, Tree tree2) {
533476 BlockTree tree2b = (BlockTree ) tree2 ;
534477 defaultPairAction (tree1 , tree2b );
535478
536- List <? extends StatementTree > s1 = tree1 .getStatements ();
537- List <? extends StatementTree > s2 = tree2b .getStatements ();
538- scanList (s1 , s2 );
479+ scanList (tree1 .getStatements (), tree2b .getStatements ());
539480 return null ;
540481 }
541482
0 commit comments