Skip to content

Commit 7457803

Browse files
authored
do not import enums or nested messages if they are only used within the message that defines them (#80)
1 parent 06da339 commit 7457803

File tree

3 files changed

+100
-4
lines changed

3 files changed

+100
-4
lines changed

protokt-codegen/src/main/kotlin/com/toasttab/protokt/codegen/impl/ImportResolver.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class ImportResolver(
7272
.filterClassesWithSamePackageName(pkg)
7373
.filterClassesWithSameNameAsMessageIn(astList)
7474
.filterClassesWithSameNameAsOneofFieldTypeIn(astList)
75+
.filterNestedClassesDefinedLocally(astList)
7576
.filterDuplicateSimpleNames(pkg) { getClassOrNone(it, ctx) }
7677

7778
private fun imports(t: TopLevelType): ImmutableSet<Import> =
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright (c) 2020 Toast Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
package com.toasttab.protokt.codegen.impl
17+
18+
import com.toasttab.protokt.codegen.algebra.AST
19+
import com.toasttab.protokt.codegen.model.Import
20+
import com.toasttab.protokt.codegen.protoc.Enum
21+
import com.toasttab.protokt.codegen.protoc.Message
22+
import com.toasttab.protokt.codegen.protoc.Oneof
23+
import com.toasttab.protokt.codegen.protoc.StandardField
24+
import com.toasttab.protokt.codegen.protoc.TopLevelType
25+
import com.toasttab.protokt.codegen.protoc.TypeDesc
26+
27+
/**
28+
* All classes defined in the scope of a message that are used as a field type
29+
* are added as possible import candidates. When those classes are only used
30+
* within the message that defines them, there is no need to import them to use
31+
* them without qualification by their enclosing message type. For example:
32+
*
33+
* class Message(
34+
* val enum: SomeEnum // does not need to be `Message.SomeEnum`
35+
* ) : KtMessage {
36+
* sealed class SomeEnum(...) : KtEnum() {
37+
* object OPTION_ONE : SomeEnum(...)
38+
* }
39+
* }
40+
*
41+
* These imports _are_ necessary in other files or equivalently when used in
42+
* messages other than that in which they are defined.
43+
*/
44+
fun Sequence<Import>.filterNestedClassesDefinedLocally(asts: List<AST<TypeDesc>>) =
45+
filterNot {
46+
it is Import.Class && it.nested &&
47+
astsUsing(it, asts).containsOnly(astDefining(it, asts))
48+
}
49+
50+
private fun astsUsing(import: Import.Class, asts: List<AST<TypeDesc>>) =
51+
asts.filter { searchTypes(it.data.type.rawType, import) }.toSet()
52+
53+
private fun searchTypes(t: TopLevelType, import: Import.Class): Boolean =
54+
t is Message &&
55+
(searchFields(t, import) ||
56+
t.nestedTypes.any { searchTypes(it, import) })
57+
58+
private fun searchFields(msg: Message, import: Import.Class) =
59+
msg.fields.any {
60+
when (it) {
61+
is StandardField -> it.typePClass == import.pClass
62+
is Oneof -> it.fields.any { f -> f.typePClass == import.pClass }
63+
}
64+
}
65+
66+
private fun astDefining(import: Import.Class, asts: List<AST<TypeDesc>>) =
67+
asts.filter {
68+
it.data.type.rawType.let { t ->
69+
import.pkg == kotlinPackage(it) &&
70+
t is Message && searchMessage(t, t.name, import)
71+
}
72+
}.let {
73+
if (it.isNotEmpty()) {
74+
// singleOrNull returns null if more than one match rather than throwing
75+
it.single()
76+
} else {
77+
null
78+
}
79+
}
80+
81+
private fun searchMessage(msg: Message, name: String, import: Import.Class): Boolean =
82+
name == import.pClass.nestedName ||
83+
msg.nestedTypes.any {
84+
val nestedName = "$name.${it.name}"
85+
when (it) {
86+
is Message -> searchMessage(it, nestedName, import)
87+
is Enum -> nestedName == import.pClass.nestedName
88+
else -> false
89+
}
90+
}
91+
92+
private fun <T> Set<T>.containsOnly(t: T) =
93+
size == 1 && contains(t)

protokt-codegen/src/main/kotlin/com/toasttab/protokt/codegen/protoc/Types.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ import com.toasttab.protokt.codegen.model.PClass
2323
import com.toasttab.protokt.ext.Protokt
2424
import com.toasttab.protokt.rt.computeTag
2525

26-
sealed class TopLevelType
26+
sealed class TopLevelType {
27+
abstract val name: String
28+
}
2729

2830
class Message(
29-
val name: String,
31+
override val name: String,
3032
val fields: List<Field>,
3133
val nestedTypes: List<TopLevelType>,
3234
val mapEntry: Boolean,
@@ -41,7 +43,7 @@ data class MessageOptions(
4143
)
4244

4345
class Enum(
44-
val name: String,
46+
override val name: String,
4547
val options: EnumOptions,
4648
val values: List<Value>,
4749
val index: Int
@@ -66,7 +68,7 @@ class EnumValueOptions(
6668
)
6769

6870
class Service(
69-
val name: String,
71+
override val name: String,
7072
val type: String,
7173
val methods: List<Method>,
7274
val deprecated: Boolean,

0 commit comments

Comments
 (0)