Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 10 additions & 17 deletions semantic/src/main/kotlin/tools/samt/semantic/SemanticModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package tools.samt.semantic

import tools.samt.common.DiagnosticController
import tools.samt.common.SourceFile
import tools.samt.parser.*
import tools.samt.parser.FileNode
import tools.samt.parser.NamedDeclarationNode
import tools.samt.parser.TypeImportNode
import tools.samt.parser.WildcardImportNode

/**
* Goals of the semantic model:
Expand Down Expand Up @@ -87,7 +90,11 @@ class SemanticModelBuilder private constructor(
is LiteralType,
is EnumType,
is RecordType,
UnknownType -> typeReference
is ServiceType,
is ProviderType,
UnknownType,
-> typeReference

is AliasType -> type.fullyResolvedType?.let { merge(typeReference, it) }
is ListType -> {
val elementType = getFullyResolvedType(type.elementType)
Expand All @@ -106,24 +113,10 @@ class SemanticModelBuilder private constructor(
null
}
}
is ServiceType -> {
controller.getOrCreateContext(typeReference.typeNode.location.source).error {
message("Type alias cannot reference service")
highlight("type alias", typeReference.typeNode.location)
}
typeReference
}
is ProviderType -> {
controller.getOrCreateContext(typeReference.typeNode.location.source).error {
message("Type alias cannot reference provider")
highlight("type alias", typeReference.typeNode.location)
}
typeReference
}
is PackageType -> {
controller.getOrCreateContext(typeReference.typeNode.location.source).error {
message("Type alias cannot reference package")
highlight("type alias", typeReference.typeNode.location)
highlight("illegal package", typeReference.typeNode.location)
}
typeReference
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,26 @@ internal class SemanticModelPostProcessor(private val controller: DiagnosticCont
checkModelType(type.valueType)
}

is AliasType -> {
val underlyingTypeReference = type.fullyResolvedType ?: return
val underlyingType = underlyingTypeReference.type
if (underlyingType is ServiceType || underlyingType is ProviderType || underlyingType is PackageType) {
controller.getOrCreateContext(typeReference.typeNode.location.source).error {
message("Type alias refers to '${underlyingType.humanReadableName}', which cannot be used in this context")
highlight("type alias", typeReference.typeNode.location)
highlight("underlying type", underlyingTypeReference.typeNode.location)
}
}

if (typeReference.isOptional && underlyingTypeReference.isOptional) {
controller.getOrCreateContext(typeReference.typeNode.location.source).warn {
message("Type alias refers to type which is already optional, ignoring '?'")
highlight("duplicate optional", typeReference.fullNode.location)
highlight("declared optional here", underlyingTypeReference.fullNode.location)
}
}
}

else -> Unit
}
}
Expand All @@ -69,6 +89,21 @@ internal class SemanticModelPostProcessor(private val controller: DiagnosticCont
block(type)
}

is AliasType -> {
val aliasedTypeReference = type.fullyResolvedType ?: return
checkBlankTypeReference(aliasedTypeReference, "service")
val aliasedType = aliasedTypeReference.type
if (aliasedType is ServiceType) {
block(aliasedType)
} else {
controller.getOrCreateContext(typeReference.typeNode.location.source).error {
message("Expected a service but type alias '${type.name}' points to '${aliasedType.humanReadableName}'")
highlight("type alias", typeReference.typeNode.location)
highlight("underlying type", aliasedTypeReference.typeNode.location)
}
}
}

is UnknownType -> Unit
else -> {
controller.getOrCreateContext(typeReference.typeNode.location.source).error {
Expand All @@ -87,6 +122,21 @@ internal class SemanticModelPostProcessor(private val controller: DiagnosticCont
block(type)
}

is AliasType -> {
val aliasedTypeReference = type.fullyResolvedType ?: return
checkBlankTypeReference(aliasedTypeReference, "provider")
val aliasedType = aliasedTypeReference.type
if (aliasedType is ProviderType) {
block(aliasedType)
} else {
controller.getOrCreateContext(typeReference.typeNode.location.source).error {
message("Expected a provider but type alias '${type.name}' points to '${aliasedType.humanReadableName}'")
highlight("type alias", typeReference.typeNode.location)
highlight("underlying type", aliasedTypeReference.typeNode.location)
}
}
}

is UnknownType -> Unit
else -> {
controller.getOrCreateContext(typeReference.typeNode.location.source).error {
Expand Down
88 changes: 88 additions & 0 deletions semantic/src/test/kotlin/tools/samt/semantic/SemanticModelTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,94 @@ class SemanticModelTest {
)
}

@Test
fun `can use service alias`() {
val source = """
package color

typealias Foo = MyService

service MyService {
foo(): String
}

provide MyEndpoint {
implements Foo { foo }

transport HTTP
}
""".trimIndent()
parseAndCheck(
source to emptyList()
)
}

@Test
fun `cannot use service alias in type model`() {
val source = """
package color

typealias Foo = MyService

service MyService {
foo(): Foo
}
""".trimIndent()
parseAndCheck(
source to listOf("Error: Type alias refers to 'MyService', which cannot be used in this context")
)
}

@Test
fun `cannot use package in alias`() {
val source = """
package color

typealias myColor = color
""".trimIndent()
parseAndCheck(
source to listOf("Error: Type alias cannot reference package")
)
}

@Test
fun `duplicate optional markers within alias definitions are reported`() {
val source = """
package people

typealias OptionalName = String? ("a-z")
typealias OptionalDeepName = OptionalName
typealias OptionalDeeperName = OptionalDeepName?
""".trimIndent()
parseAndCheck(
source to listOf(
"Warning: Type is already optional, ignoring '?'",
)
)
}

@Test
fun `duplicate optional markers when referencing alias types are reported`() {
val source = """
package people

typealias OptionalName = String? ("a-z")
typealias OptionalDeepName = OptionalName

record Human {
firstName: OptionalName
lastName: OptionalName?
middleName: OptionalDeepName?
}
""".trimIndent()
parseAndCheck(
source to listOf(
"Warning: Type alias refers to type which is already optional, ignoring '?'",
"Warning: Type alias refers to type which is already optional, ignoring '?'",
)
)
}

@Test
fun `cannot use type aliases with cyclic references`() {
val source = """
Expand Down