Skip to content

Commit cfc6564

Browse files
Merge pull request #16 from samtkit/detect-duplicate-implements-and-uses
feat(semantic): prevent duplicate uses and implements statements
2 parents 95aef05 + 074408f commit cfc6564

File tree

2 files changed

+58
-0
lines changed

2 files changed

+58
-0
lines changed

semantic/src/main/kotlin/tools/samt/semantic/SemanticModelPostProcessor.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package tools.samt.semantic
22

33
import tools.samt.common.DiagnosticController
4+
import tools.samt.common.Location
45

56
internal class SemanticModelPostProcessor(private val controller: DiagnosticController) {
67
/**
@@ -139,8 +140,18 @@ internal class SemanticModelPostProcessor(private val controller: DiagnosticCont
139140
}
140141

141142
private fun checkProvider(provider: ProviderType) {
143+
val implementsTypes = mutableMapOf<ServiceType, Location>()
142144
provider.implements.forEach { implements ->
143145
checkServiceType(implements.service) { type ->
146+
implementsTypes.putIfAbsent(type, implements.definition.location)?.let { existingLocation ->
147+
controller.createContext(implements.definition.location.source).error {
148+
message("Service '${type.name}' already implemented")
149+
highlight("duplicate implements", implements.definition.location)
150+
highlight("previous implements", existingLocation)
151+
}
152+
return@forEach
153+
}
154+
144155
implements.operations = if (implements.definition.serviceOperationNames.isEmpty()) {
145156
type.operations
146157
} else {
@@ -162,9 +173,19 @@ internal class SemanticModelPostProcessor(private val controller: DiagnosticCont
162173
}
163174

164175
private fun checkConsumer(consumer: ConsumerType) {
176+
val usesTypes = mutableMapOf<ServiceType, Location>()
165177
checkProviderType(consumer.provider) { providerType ->
166178
consumer.uses.forEach { uses ->
167179
checkServiceType(uses.service) { type ->
180+
usesTypes.putIfAbsent(type, uses.definition.location)?.let { existingLocation ->
181+
controller.createContext(uses.definition.location.source).error {
182+
message("Service '${type.name}' already used")
183+
highlight("duplicate uses", uses.definition.location)
184+
highlight("previous uses", existingLocation)
185+
}
186+
return@forEach
187+
}
188+
168189
val matchingImplements =
169190
providerType.implements.find { (it.service as ResolvedTypeReference).type == type }
170191
if (matchingImplements == null) {

semantic/src/test/kotlin/tools/samt/semantic/SemanticModelTest.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,43 @@ class SemanticModelTest {
650650
service to emptyList(),
651651
)
652652
}
653+
654+
@Test
655+
fun `cannot use or implement same service multiple times`() {
656+
val consumer = """
657+
import providers.FooEndpoint as Provider
658+
import services.FooService as Service
659+
660+
package consumers
661+
662+
consume Provider {
663+
uses Service { foo }
664+
uses services.FooService
665+
}
666+
""".trimIndent()
667+
val provider = """
668+
import services.*
669+
package providers
670+
671+
provide FooEndpoint {
672+
implements FooService
673+
implements FooService
674+
transport HTTP
675+
}
676+
""".trimIndent()
677+
val service = """
678+
package services
679+
680+
service FooService {
681+
foo()
682+
}
683+
""".trimIndent()
684+
parseAndCheck(
685+
consumer to listOf("Error: Service 'FooService' already used"),
686+
provider to listOf("Error: Service 'FooService' already implemented"),
687+
service to emptyList(),
688+
)
689+
}
653690
}
654691

655692
private fun parseAndCheck(

0 commit comments

Comments
 (0)