Skip to content

Commit a503611

Browse files
nicolas-guichardantonsviridov-src
authored andcommitted
Improve handling of anonymous objects
We don't want to call all anonymous objects <anonymous>, or we can't desambiguate between two anonymous objects from the same package. We emit anonymous objects just like regular classes, but take care to point at the `object` keyword for the class and primary constructor.
1 parent a75b093 commit a503611

File tree

4 files changed

+231
-13
lines changed

4 files changed

+231
-13
lines changed

semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/AnalyzerCheckers.kt

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
2424
import org.jetbrains.kotlin.fir.resolve.calls.FirSyntheticFunctionSymbol
2525
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
2626
import org.jetbrains.kotlin.fir.resolve.toClassLikeSymbol
27+
import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousObjectSymbol
2728
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
2829
import org.jetbrains.kotlin.lexer.KtTokens
2930
import org.jetbrains.kotlin.name.ClassId
@@ -54,8 +55,8 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio
5455
open class AnalyzerDeclarationCheckers(sourceroot: Path) : DeclarationCheckers() {
5556
override val fileCheckers: Set<FirFileChecker> =
5657
setOf(SemanticFileChecker(sourceroot), SemanticImportsChecker())
57-
override val regularClassCheckers: Set<FirRegularClassChecker> =
58-
setOf(SemanticRegularClassChecker())
58+
override val classLikeCheckers: Set<FirClassLikeChecker> =
59+
setOf(SemanticClassLikeChecker())
5960
override val constructorCheckers: Set<FirConstructorChecker> =
6061
setOf(SemanticConstructorChecker())
6162
override val simpleFunctionCheckers: Set<FirSimpleFunctionChecker> =
@@ -166,23 +167,33 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio
166167
}
167168
}
168169

169-
private class SemanticRegularClassChecker : FirRegularClassChecker(MppCheckerKind.Common) {
170+
private class SemanticClassLikeChecker : FirClassLikeChecker(MppCheckerKind.Common) {
170171
@OptIn(ExperimentalContracts::class)
171172
override fun check(
172-
declaration: FirRegularClass,
173+
declaration: FirClassLikeDeclaration,
173174
context: CheckerContext,
174175
reporter: DiagnosticReporter
175176
) {
176177
val source = declaration.source ?: return
177178
val ktFile = context.containingFile?.sourceFile ?: return
178179
val visitor = visitors[ktFile]
179-
visitor?.visitClassOrObject(declaration, getIdentifier(source), context)
180-
181-
for (superType in declaration.superTypeRefs) {
182-
val superSymbol = superType.toClassLikeSymbol(context.session)
183-
val superSource = superType.source
184-
if (superSymbol != null && superSource != null) {
185-
visitor?.visitClassReference(superSymbol, superSource, context)
180+
val objectKeyword = if (declaration is FirAnonymousObject) {
181+
source
182+
.treeStructure
183+
.findChildByType(source.lighterASTNode, KtTokens.OBJECT_KEYWORD)
184+
?.toKtLightSourceElement(source.treeStructure)
185+
} else {
186+
null
187+
}
188+
visitor?.visitClassOrObject(declaration, objectKeyword ?: getIdentifier(source), context)
189+
190+
if (declaration is FirClass) {
191+
for (superType in declaration.superTypeRefs) {
192+
val superSymbol = superType.toClassLikeSymbol(context.session)
193+
val superSource = superType.source
194+
if (superSymbol != null && superSource != null) {
195+
visitor?.visitClassReference(superSymbol, superSource, context)
196+
}
186197
}
187198
}
188199
}
@@ -210,7 +221,16 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio
210221
.findChildByType(source.lighterASTNode, KtTokens.CONSTRUCTOR_KEYWORD)
211222
?.toKtLightSourceElement(source.treeStructure)
212223

213-
visitor?.visitPrimaryConstructor(declaration, constructorKeyboard ?: getIdentifier(klassSource), context)
224+
val objectKeyword = if (klass is FirAnonymousObjectSymbol) {
225+
source
226+
.treeStructure
227+
.findChildByType(source.lighterASTNode, KtTokens.OBJECT_KEYWORD)
228+
?.toKtLightSourceElement(source.treeStructure)
229+
} else {
230+
null
231+
}
232+
233+
visitor?.visitPrimaryConstructor(declaration, constructorKeyboard ?: objectKeyword ?: getIdentifier(klassSource), context)
214234
} else {
215235
visitor?.visitSecondaryConstructor(declaration, getIdentifier(source), context)
216236
}

semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SemanticdbVisitor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class SemanticdbVisitor(
5959
cache[firClassSymbol].with(firClassSymbol).emitAll(element, Role.REFERENCE, context)
6060
}
6161

62-
fun visitClassOrObject(firClass: FirClass, element: KtSourceElement, context: CheckerContext) {
62+
fun visitClassOrObject(firClass: FirClassLikeDeclaration, element: KtSourceElement, context: CheckerContext) {
6363
cache[firClass.symbol].with(firClass.symbol).emitAll(element, Role.DEFINITION, context)
6464
}
6565

semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SymbolsCache.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ class GlobalSymbolsCache(testing: Boolean = false) : Iterable<Symbol> {
139139
@OptIn(SymbolInternals::class)
140140
private fun semanticdbDescriptor(symbol: FirBasedSymbol<*>): SemanticdbSymbolDescriptor {
141141
return when {
142+
symbol is FirAnonymousObjectSymbol ->
143+
symbol.source?.let { source ->
144+
SemanticdbSymbolDescriptor(Kind.TYPE, "<anonymous object at ${source.startOffset}>")
145+
} ?: SemanticdbSymbolDescriptor.NONE
142146
symbol is FirClassLikeSymbol ->
143147
SemanticdbSymbolDescriptor(Kind.TYPE, symbol.classId.shortClassName.asString())
144148
symbol is FirPropertyAccessorSymbol && symbol.isSetter ->

semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/AnalyzerTest.kt

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,200 @@ class AnalyzerTest {
250250
assertSoftly(document.symbolsList) { withClue(this) { symbols.forEach(::shouldContain) } }
251251
}
252252

253+
@Test
254+
fun `anonymous object`(@TempDir path: Path) {
255+
val document =
256+
compileSemanticdb(
257+
path,
258+
"""
259+
package sample
260+
261+
interface Interface {
262+
fun foo()
263+
}
264+
265+
fun main() {
266+
val a = object : Interface {
267+
override fun foo() {}
268+
}
269+
val b = object : Interface {
270+
override fun foo() {}
271+
}
272+
}
273+
""")
274+
275+
val occurrences =
276+
arrayOf(
277+
SymbolOccurrence {
278+
role = Role.REFERENCE
279+
symbol = "sample/"
280+
range {
281+
startLine = 0
282+
startCharacter = 8
283+
endLine = 0
284+
endCharacter = 14
285+
}
286+
},
287+
SymbolOccurrence {
288+
role = Role.DEFINITION
289+
symbol = "sample/Interface#"
290+
range {
291+
startLine = 2
292+
startCharacter = 10
293+
endLine = 2
294+
endCharacter = 19
295+
}
296+
},
297+
SymbolOccurrence {
298+
role = Role.DEFINITION
299+
symbol = "sample/Interface#foo()."
300+
range {
301+
startLine = 3
302+
startCharacter = 8
303+
endLine = 3
304+
endCharacter = 11
305+
}
306+
},
307+
SymbolOccurrence {
308+
role = Role.DEFINITION
309+
symbol = "sample/`<anonymous object at 80>`#"
310+
range {
311+
startLine = 7
312+
startCharacter = 12
313+
endLine = 7
314+
endCharacter = 18
315+
}
316+
},
317+
SymbolOccurrence {
318+
role = Role.DEFINITION
319+
symbol = "sample/`<anonymous object at 80>`#`<init>`()."
320+
range {
321+
startLine = 7
322+
startCharacter = 12
323+
endLine = 7
324+
endCharacter = 18
325+
}
326+
},
327+
SymbolOccurrence {
328+
role = Role.REFERENCE
329+
symbol = "sample/Interface#"
330+
range {
331+
startLine = 7
332+
startCharacter = 21
333+
endLine = 7
334+
endCharacter = 30
335+
}
336+
},
337+
SymbolOccurrence {
338+
role = Role.DEFINITION
339+
symbol = "sample/`<anonymous object at 80>`#foo()."
340+
range {
341+
startLine = 8
342+
startCharacter = 21
343+
endLine = 8
344+
endCharacter = 24
345+
}
346+
},
347+
SymbolOccurrence {
348+
role = Role.DEFINITION
349+
symbol = "sample/`<anonymous object at 149>`#"
350+
range {
351+
startLine = 10
352+
startCharacter = 12
353+
endLine = 10
354+
endCharacter = 18
355+
}
356+
},
357+
SymbolOccurrence {
358+
role = Role.DEFINITION
359+
symbol = "sample/`<anonymous object at 149>`#`<init>`()."
360+
range {
361+
startLine = 10
362+
startCharacter = 12
363+
endLine = 10
364+
endCharacter = 18
365+
}
366+
},
367+
SymbolOccurrence {
368+
role = Role.REFERENCE
369+
symbol = "sample/Interface#"
370+
range {
371+
startLine = 10
372+
startCharacter = 21
373+
endLine = 10
374+
endCharacter = 30
375+
}
376+
},
377+
SymbolOccurrence {
378+
role = Role.DEFINITION
379+
symbol = "sample/`<anonymous object at 149>`#foo()."
380+
range {
381+
startLine = 11
382+
startCharacter = 21
383+
endLine = 11
384+
endCharacter = 24
385+
}
386+
},
387+
)
388+
assertSoftly(document.occurrencesList) {
389+
withClue(this) { occurrences.forEach(::shouldContain) }
390+
}
391+
392+
val symbols =
393+
arrayOf(
394+
SymbolInformation {
395+
symbol = "sample/Interface#"
396+
displayName = "Interface"
397+
language = KOTLIN
398+
documentation {
399+
message = "```kotlin\npublic abstract interface Interface : Any\n```"
400+
format = Semanticdb.Documentation.Format.MARKDOWN
401+
}
402+
},
403+
SymbolInformation {
404+
symbol = "sample/`<anonymous object at 80>`#"
405+
displayName = "<anonymous>"
406+
language = KOTLIN
407+
documentation {
408+
message = "```kotlin\nobject : Interface\n```"
409+
format = Semanticdb.Documentation.Format.MARKDOWN
410+
}
411+
addOverriddenSymbols("sample/Interface#")
412+
},
413+
SymbolInformation {
414+
symbol = "sample/`<anonymous object at 80>`#foo()."
415+
displayName = "foo"
416+
language = KOTLIN
417+
documentation {
418+
message = "```kotlin\npublic open override fun foo(): Unit\n```"
419+
format = Semanticdb.Documentation.Format.MARKDOWN
420+
}
421+
addOverriddenSymbols("sample/Interface#foo().")
422+
},
423+
SymbolInformation {
424+
symbol = "sample/`<anonymous object at 149>`#"
425+
displayName = "<anonymous>"
426+
language = KOTLIN
427+
documentation {
428+
message = "```kotlin\nobject : Interface\n```"
429+
format = Semanticdb.Documentation.Format.MARKDOWN
430+
}
431+
addOverriddenSymbols("sample/Interface#")
432+
},
433+
SymbolInformation {
434+
symbol = "sample/`<anonymous object at 149>`#foo()."
435+
displayName = "foo"
436+
language = KOTLIN
437+
documentation {
438+
message = "```kotlin\npublic open override fun foo(): Unit\n```"
439+
format = Semanticdb.Documentation.Format.MARKDOWN
440+
}
441+
addOverriddenSymbols("sample/Interface#foo().")
442+
},
443+
)
444+
assertSoftly(document.symbolsList) { withClue(this) { symbols.forEach(::shouldContain) } }
445+
}
446+
253447
@Test
254448
fun `exception test`(@TempDir path: Path) {
255449
val buildPath = File(path.resolve("build").toString()).apply { mkdir() }

0 commit comments

Comments
 (0)