-
Notifications
You must be signed in to change notification settings - Fork 243
/
MarkedSources.java
429 lines (407 loc) · 16.6 KB
/
MarkedSources.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
/*
* Copyright 2017 The Kythe Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devtools.kythe.analyzers.java;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
import com.google.devtools.kythe.platform.java.helpers.SignatureGenerator;
import com.google.devtools.kythe.proto.MarkedSource;
import com.google.devtools.kythe.proto.Storage.VName;
import com.google.devtools.kythe.util.KytheURI;
import com.sun.tools.javac.code.BoundKind;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.TypeTag;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import org.checkerframework.checker.nullness.qual.Nullable;
/** {@link MarkedSource} utility class. */
public final class MarkedSources {
private MarkedSources() {}
/** {@link MarkedSource} for Java "tapp" nodes of the form {@code C<T1, T2, T3...>}. */
public static final MarkedSource GENERIC_TAPP;
/** {@link MarkedSource} for Java "tapp" nodes of the form {@code C[]}. */
public static final MarkedSource ARRAY_TAPP;
/** {@link MarkedSource} for Java "tapp" nodes of the form {@code R(T1, T2, T3...)}. */
public static final MarkedSource FN_TAPP;
/** {@link MarkedSource} for Java "tapp" nodes of the form {@code R C::(T1, T2, T3...)}. */
public static final MarkedSource METHOD_TAPP;
static {
MarkedSource.Builder genericTAppBuilder =
MarkedSource.newBuilder().setKind(MarkedSource.Kind.TYPE);
genericTAppBuilder.addChildBuilder().setKind(MarkedSource.Kind.LOOKUP_BY_PARAM);
genericTAppBuilder
.addChildBuilder()
.setKind(MarkedSource.Kind.PARAMETER_LOOKUP_BY_PARAM)
.setLookupIndex(1)
.setPreText("<")
.setPostText(">")
.setPostChildText(", ");
GENERIC_TAPP = genericTAppBuilder.build();
}
static {
MarkedSource.Builder arrayTAppBuilder =
MarkedSource.newBuilder().setKind(MarkedSource.Kind.TYPE);
arrayTAppBuilder
.addChildBuilder()
.setKind(MarkedSource.Kind.PARAMETER_LOOKUP_BY_PARAM)
.setLookupIndex(1)
.setPostText("[]");
ARRAY_TAPP = arrayTAppBuilder.build();
}
static {
MarkedSource.Builder fnTAppBuilder = MarkedSource.newBuilder().setKind(MarkedSource.Kind.TYPE);
fnTAppBuilder.addChildBuilder().setKind(MarkedSource.Kind.LOOKUP_BY_PARAM).setLookupIndex(1);
fnTAppBuilder
.addChildBuilder()
.setKind(MarkedSource.Kind.PARAMETER_LOOKUP_BY_PARAM)
.setLookupIndex(2)
.setPreText("(")
.setPostText(")")
.setPostChildText(", ");
FN_TAPP = fnTAppBuilder.build();
}
static {
MarkedSource.Builder methodTAppBuilder =
MarkedSource.newBuilder().setKind(MarkedSource.Kind.TYPE);
methodTAppBuilder
.addChildBuilder()
.setKind(MarkedSource.Kind.BOX)
.setPostText(" ")
.addChildBuilder()
.setKind(MarkedSource.Kind.LOOKUP_BY_PARAM)
.setLookupIndex(1);
methodTAppBuilder
.addChildBuilder()
.setKind(MarkedSource.Kind.BOX)
.setPostText("::")
.addChildBuilder()
.setKind(MarkedSource.Kind.LOOKUP_BY_PARAM)
.setLookupIndex(2);
methodTAppBuilder
.addChildBuilder()
.setKind(MarkedSource.Kind.PARAMETER_LOOKUP_BY_PARAM)
.setLookupIndex(3)
.setPreText("(")
.setPostText(")")
.setPostChildText(", ");
METHOD_TAPP = methodTAppBuilder.build();
}
/** Returns a {@link MarkedSource} instance for a {@link Symbol}. */
static MarkedSource construct(
SignatureGenerator signatureGenerator,
Symbol sym,
MarkedSource.@Nullable Builder msBuilder,
@Nullable Iterable<MarkedSource> postChildren,
Function<Symbol, Optional<VName>> symNames) {
MarkedSource markedType = markType(signatureGenerator, sym, symNames);
return construct(signatureGenerator, sym, msBuilder, postChildren, markedType);
}
private static MarkedSource construct(
SignatureGenerator signatureGenerator,
Symbol sym,
MarkedSource.@Nullable Builder msBuilder,
@Nullable Iterable<MarkedSource> postChildren,
@Nullable MarkedSource markedType) {
MarkedSource.Builder markedSource = msBuilder == null ? MarkedSource.newBuilder() : msBuilder;
ImmutableSortedSet<Modifier> modifiers = getModifiers(sym);
MarkedSource.Builder mods = null;
if (!modifiers.isEmpty()
|| ImmutableSet.of(
ElementKind.CLASS, ElementKind.ENUM, ElementKind.INTERFACE, ElementKind.PACKAGE)
.contains(sym.getKind())) {
mods = markedSource.addChildBuilder().setPostChildText(" ").setAddFinalListToken(true);
for (Modifier m : modifiers) {
mods.addChild(
MarkedSource.newBuilder()
.setKind(MarkedSource.Kind.MODIFIER)
.setPreText(m.toString())
.build());
}
}
if (sym.getKind() == ElementKind.METHOD && !sym.getTypeParameters().isEmpty()) {
MarkedSource.Builder typeParams =
markedSource
.addChildBuilder()
.setKind(MarkedSource.Kind.TYPE)
.setPostChildText(" ")
.setAddFinalListToken(true)
.addChildBuilder()
.setKind(MarkedSource.Kind.PARAMETER)
.setPreText("<")
.setPostText(">")
.setPostChildText(", ");
for (Symbol t : sym.getTypeParameters()) {
typeParams.addChildBuilder().setKind(MarkedSource.Kind.IDENTIFIER).setPreText(t.toString());
}
}
if (markedType != null && sym.getKind() != ElementKind.CONSTRUCTOR) {
markedSource.addChild(markedType);
}
String identToken = buildContext(markedSource.addChildBuilder(), sym, signatureGenerator);
switch (sym.getKind()) {
case TYPE_PARAMETER:
markedSource
.addChildBuilder()
.setKind(MarkedSource.Kind.IDENTIFIER)
.setPreText("" + sym.getSimpleName());
break;
case CONSTRUCTOR:
case METHOD:
ClassSymbol enclClass = sym.enclClass();
String methodName;
if (sym.getKind() == ElementKind.CONSTRUCTOR && enclClass != null) {
methodName = enclClass.getSimpleName().toString();
} else {
methodName = sym.getSimpleName().toString();
}
markedSource.addChildBuilder().setKind(MarkedSource.Kind.IDENTIFIER).setPreText(methodName);
markedSource
.addChildBuilder()
.setKind(MarkedSource.Kind.PARAMETER_LOOKUP_BY_PARAM)
.setPreText("(")
.setPostChildText(", ")
.setPostText(")");
break;
case ENUM:
case CLASS:
case INTERFACE:
mods.addChild(
MarkedSource.newBuilder()
.setKind(MarkedSource.Kind.MODIFIER)
.setPreText(sym.getKind().toString().toLowerCase())
.build());
markedSource.addChildBuilder().setKind(MarkedSource.Kind.IDENTIFIER).setPreText(identToken);
if (!sym.getTypeParameters().isEmpty()) {
markedSource
.addChildBuilder()
.setKind(MarkedSource.Kind.PARAMETER_LOOKUP_BY_TPARAM)
.setPreText("<")
.setPostText(">")
.setPostChildText(", ");
}
break;
default:
markedSource.addChildBuilder().setKind(MarkedSource.Kind.IDENTIFIER).setPreText(identToken);
break;
}
if (postChildren != null) {
postChildren.forEach(markedSource::addChild);
}
return markedSource.build();
}
private static ImmutableSortedSet<Modifier> getModifiers(Symbol sym) {
ImmutableSortedSet<Modifier> modifiers = ImmutableSortedSet.copyOf(sym.getModifiers());
switch (sym.getKind()) {
case ENUM:
// Remove synthesized enum modifiers
return ImmutableSortedSet.copyOf(
Sets.difference(modifiers, ImmutableSet.of(Modifier.STATIC, Modifier.FINAL)));
case INTERFACE:
// Remove synthesized interface modifiers
return ImmutableSortedSet.copyOf(
Sets.difference(modifiers, ImmutableSet.of(Modifier.ABSTRACT, Modifier.STATIC)));
case ENUM_CONSTANT:
// Remove synthesized enum constantc modifiers
return ImmutableSortedSet.copyOf(
Sets.difference(
modifiers, ImmutableSet.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)));
default:
return modifiers;
}
}
/**
* Sets the provided {@link MarkedSource.Builder} to a CONTEXT node, populating it with the
* fully-qualified parent scope for sym. Returns the identifier corresponding to sym.
*/
private static String buildContext(
MarkedSource.Builder context, Symbol sym, SignatureGenerator signatureGenerator) {
context.setKind(MarkedSource.Kind.CONTEXT).setPostChildText(".").setAddFinalListToken(true);
String identToken = getIdentToken(sym, signatureGenerator);
Symbol parent = getQualifiedNameParent(sym);
List<MarkedSource> parents = new ArrayList<>();
while (parent != null) {
String parentName = getIdentToken(parent, signatureGenerator);
if (!parentName.isEmpty()) {
parents.add(
MarkedSource.newBuilder()
.setKind(MarkedSource.Kind.IDENTIFIER)
.setPreText(parentName)
.build());
}
parent = getQualifiedNameParent(parent);
}
for (int i = 0; i < parents.size(); ++i) {
context.addChild(parents.get(parents.size() - i - 1));
}
return identToken;
}
/**
* Returns a {@link MarkedSource} instance for sym's type (or its return type, if sym is a
* method). If there is no appropriate type for sym, returns {@code null}. Generates links with
* {@code signatureGenerator}.
*/
private static @Nullable MarkedSource markType(
SignatureGenerator signatureGenerator,
Symbol sym,
Function<Symbol, Optional<VName>> symNames) {
// TODO(zarko): Mark up any annotations.
Type type = sym.type;
if (type == null || sym == type.tsym) {
return null;
}
if (type.getReturnType() != null) {
type = type.getReturnType();
}
String postTypeIdText = "";
if (type.hasTag(TypeTag.ARRAY) && ((Type.ArrayType) type).elemtype != null) {
postTypeIdText = "[]";
type = ((Type.ArrayType) type).elemtype;
}
MarkedSource.Builder builder =
MarkedSource.newBuilder().setKind(MarkedSource.Kind.TYPE).setPostText(" ");
if (type.hasTag(TypeTag.CLASS)) {
MarkedSource.Builder classIdentParent = builder;
if (!postTypeIdText.isEmpty()) {
classIdentParent = builder.addChildBuilder().setPostText(postTypeIdText);
}
addClassIdentifier(type, classIdentParent, signatureGenerator, symNames);
} else {
builder
.addChildBuilder()
.setKind(MarkedSource.Kind.IDENTIFIER)
.setPreText(type.toString())
.setPostText(postTypeIdText);
}
return builder.build();
}
private static void addClassIdentifier(
Type type,
MarkedSource.Builder parent,
SignatureGenerator signatureGenerator,
Function<Symbol, Optional<VName>> symNames) {
// Add the class CONTEXT (i.e. package) and class IDENTIFIER (i.e. simple name).
// The qualifiedName BOX is used to restrict the Link added below.
MarkedSource.Builder qualifiedName = parent.addChildBuilder();
String identToken =
buildContext(qualifiedName.addChildBuilder(), type.tsym, signatureGenerator);
qualifiedName.addChildBuilder().setKind(MarkedSource.Kind.IDENTIFIER).setPreText(identToken);
// Add a link to the Kythe semantic node for the class.
symNames
.apply(type.tsym)
.map(KytheURI::asString)
.ifPresent(ticket -> qualifiedName.addLinkBuilder().addDefinition(ticket));
// Possibly add a PARAMETER node for the class type arguments.
if (!type.getTypeArguments().isEmpty()) {
MarkedSource.Builder typeArgs =
parent
.addChildBuilder()
.setKind(MarkedSource.Kind.PARAMETER)
.setPreText("<")
.setPostChildText(", ")
.setPostText(">");
for (Type arg : type.getTypeArguments()) {
switch (arg.getTag()) {
case CLASS:
addClassIdentifier(arg, typeArgs.addChildBuilder(), signatureGenerator, symNames);
break;
case WILDCARD:
Type.WildcardType wild = (Type.WildcardType) arg;
// JDK19+ changes the isBound() accessor to include
// extends Object bounds. This causes gratuitous differences
// in documentation and tests.
if (wild.kind == BoundKind.UNBOUND) {
typeArgs.addChildBuilder().setPreText(wild.kind.toString());
} else {
MarkedSource.Builder boundedWild = typeArgs.addChildBuilder();
boundedWild.addChildBuilder().setPreText(wild.kind.toString());
addClassIdentifier(wild.type, boundedWild, signatureGenerator, symNames);
}
break;
default:
typeArgs.addChildBuilder().setPreText(arg.toString());
}
}
}
}
/**
* The only place the integer index for nested classes/anonymous classes is stored is in the
* flatname of the symbol. (This index is determined at compile time using linear search; see
* 'localClassName' in Check.java). The simple name can't be relied on; for nested classes it
* drops the name of the parent class (so 'pkg.OuterClass$Inner' yields only 'Inner') and for
* anonymous classes it's blank. For multiply-nested classes, we'll see tokens like
* 'OuterClass$Inner$1$1'.
*/
private static String getIdentToken(Symbol sym, SignatureGenerator signatureGenerator) {
// If the symbol represents the generated `Array` class, replace it with the actual
// array type, if we have it.
if (SignatureGenerator.isArrayHelperClass(sym) && signatureGenerator != null) {
return signatureGenerator.getArrayTypeName();
}
String flatName = sym.flatName().toString();
int lastDot = flatName.lastIndexOf('.');
// A$1 is a valid variable/method name, so make sure we only look at $ in class names.
int lastCash = (sym instanceof ClassSymbol) ? flatName.lastIndexOf('$') : -1;
int lastTok = Math.max(lastDot, lastCash);
String identToken = lastTok < 0 ? flatName : flatName.substring(lastTok + 1);
if (!identToken.isEmpty() && Character.isDigit(identToken.charAt(0))) {
if (sym.name.isEmpty()) {
identToken = "(anon " + identToken + ")";
} else {
identToken = sym.name.toString();
}
}
return identToken;
}
/**
* Returns the Symbol for sym's parent in qualified names, assuming that we'll be using
* getIdentToken() to print nodes.
*
* <p>We're going through this extra effort to try and give people unsurprising qualified names.
* To do that we have to deal with javac's mangling (in {@link #getIdentToken} above), since for
* anonymous classes javac only stores mangled symbols. The code as written will emit only dotted
* fully-qualified names, even for inner or anonymous classes, and considers concrete type,
* package, or method names to be appropriate dot points. (If we weren't careful here we might,
* for example, observe nodes in a qualified name corresponding to variables that are initialized
* to anonymous classes.) This reflects the nesting structure from the Java side, not the JVM
* side.
*/
private static @Nullable Symbol getQualifiedNameParent(Symbol sym) {
sym = sym.owner;
while (sym != null) {
switch (sym.kind) {
case TYP:
if (!sym.type.hasTag(TypeTag.TYPEVAR)) {
return sym;
}
break;
case PCK:
case MTH:
return sym;
// TODO(#1845): resolve non-exhaustive switch statements w/o defaults
default:
break;
}
sym = sym.owner;
}
return null;
}
}