gotlin/README.md
2026-08-28 00:02:32 +02:00

442 lines
9.9 KiB
Markdown

# Gotlin
Gotlin is a Kotlin-flavored language implemented in Go. It compiles typed
Gotlin source to Go and uses the Go toolchain for binaries, packages, and
interop.
```kotlin
package main
fun fibonacci(value: Int): Int {
if (value < 2) { return value }
return fibonacci(value - 1) + fibonacci(value - 2)
}
fun main() {
println(fibonacci(10))
}
```
## Compiler pipeline
```text
Lexer
-> parser and syntax AST
-> package resolver and structural TypeRef resolution
-> lexical symbols, type checking, effects, and diagnostics
-> typed HIR
-> Go emission
-> Go compiler
```
Semantic types distinguish named values, Gotlin classes, nullable values, Go
pointers, functions, generics, tuples, and imported Gotlin classes. A Gotlin
class remains `User` throughout semantic analysis; only Type-to-Go lowering
chooses the `*User` representation.
Separately compiled Gotlin packages expose a versioned `.gti.json` interface
containing classes, enums, signatures, generics, and inferred effects. The
compiler and LSP load package interfaces before using `go/types` for ordinary
Go dependencies.
## Functions and values
```kotlin
fun greet(name: String): String {
val message = "Hello " + name
return message
}
fun doubled(value: Int) = value * 2
```
`val` is immutable and `var` is mutable:
```kotlin
val accountId = "account-1"
var attempts = 0
attempts += 1
```
Supported control flow includes `if`, `else`, `while`, `for`, `try`, `catch`,
`throw`, exhaustive `match`, and `defer`.
## Classes and interfaces
Gotlin classes are reference-valued by default:
```kotlin
interface Greeter {
fun greet(name: String): String
}
class PrefixGreeter(val prefix: String): Greeter {
fun greet(name: String) = prefix + name
}
fun create(): PrefixGreeter = PrefixGreeter("Hello ")
```
Use `*` only for Go pointer types:
```kotlin
fun handle(request: *http.Request, pool: *pgxpool.Pool) { }
```
Imported Gotlin classes retain reference semantics across package boundaries:
```kotlin
fun publish(lifecycle: platform.Lifecycle) { }
```
## Null safety
Types are non-nullable unless marked with `?`:
```kotlin
fun email(user: User?): String {
if (user == null) { return "missing" }
return user.email
}
```
Safe access and non-null assertions are available:
```kotlin
val city: String? = user?.address?.city
val required: User = optionalUser!!
```
The semantic analyzer smart-casts values after null checks and guard clauses.
## Data classes and JSON
```kotlin
@jsonNaming(camelCase)
data class AccountReply(
var accountId: String,
var availableBalance: Double
)
```
Supported JSON policies are `snakeCase`, `camelCase`, `pascalCase`, and
`kebabCase`.
```kotlin
val body = json.marshal(reply).unwrap()
val decoded = json.decode<AccountReply>(body).unwrap()
```
Resources can be embedded at package scope:
```kotlin
import embed
@embed("static/*") val assets: embed.FS
```
## Enums and match
Enums support payloadless and payload variants:
```kotlin
enum PaymentResult {
Accepted(String)
Rejected(String)
Pending
}
```
Matches are exhaustive and may return values:
```kotlin
fun description(result: PaymentResult) = match (result) {
PaymentResult.Accepted(id) -> "accepted " + id
PaymentResult.Rejected(reason) -> "rejected " + reason
PaymentResult.Pending -> "pending"
}
```
Payloadless enums are represented as exact string-backed values, making them
suitable for JSON and PostgreSQL columns.
## Result error handling
```kotlin
fun parse(value: String): Result<Int, Error> = strconv.atoi(value)
fun doubled(value: String): Result<Int, Error> {
val parsed = strconv.atoi(value)?
return Result.Ok(parsed * 2)
}
```
Available operations include `?`, `unwrap()`, `unwrapOr(value)`, explicit
destructuring, and exhaustive `Result` matching. Go `(T, error)` and error-only
returns adapt to `Result` when required by context.
## Generics and lambdas
```kotlin
data class Box<T>(var value: T) {
fun get(): T = value
}
fun identity<T>(value: T): T = value
val inferred = identity(42)
val explicit = identity<String>("value")
val boxed = Box("text")
```
Higher-order functions use Kotlin-style function types and trailing lambdas:
```kotlin
fun transform<T, R>(value: T, block: (T) -> R): R = block(value)
val answer = transform(21) { value ->
value * 2
}
```
The final expression is returned automatically for value lambdas. Unit lambdas
execute their final expression as a statement. Explicit `return` is also
supported.
## Structured concurrency and context
Coroutine effects are inferred from direct and transitive calls:
```kotlin
fun poll() {
while (isActive()) {
receiveEvents()
delay(1000)
}
}
fun main() {
runBlocking {
launch { poll() }
}
}
```
`launch` creates a structured Unit child. `async<T>` creates a typed deferred
child. Scopes wait for children, propagate failures, and cancel siblings.
Available operations include `runBlocking`, `withContext`, `coroutineScope`,
`launch`, `async`, `await`, `delay`, `withTimeout`, `isActive`, and
`coroutineContext`.
HTTP request contexts can establish an ambient scope with an expression-bodied
function:
```kotlin
fun handle(request: *http.Request) = withContext(request.context()) {
service.processRequest()
}
```
Go calls whose first parameter is `context.Context` receive the ambient context
automatically when that argument is omitted:
```kotlin
fun command(): *exec.Cmd = exec.commandContext("date")
```
Passing an explicit context suppresses automatic injection.
## Collections and channels
```kotlin
val names = listOf<String>("Ada", "Linus")
val scores = mapOf<String, Int>("Ada", 10, "Linus", 8)
val channel = Channel<String>(1)
channel.send("ready")
val message = channel.read()
```
Collection types include `List<T>`, `MutableList<T>`, `Map<K, V>`, and
`MutableMap<K, V>`.
## Structural mapping
`mapTo` maps compatible classes, enums, collections, and nullable values:
```kotlin
fun response(account: AccountEntity): AccountResponse = account.mapTo()
val response = account.mapTo<AccountResponse>()
```
Fields are matched by name and mappings are validated recursively with
path-specific compile errors.
## Typed PostgreSQL DSL
SQL rows are data classes with table metadata:
```kotlin
@table("accounts")
data class AccountRow(
@generated @id var id: String,
var customerId: String,
var balance: Double,
var closedAt: time.Time?
)
```
### Queries, aliases, joins, groups, and aggregates
```kotlin
@table("customers")
data class CustomerRow(@id var id: String, var name: String)
data class AccountSummary(
var customerId: String,
var customerName: String,
var total: Double,
var entries: Long
)
fun summaries(minimum: Double): GotlinSQLQuery = sql.from<AccountRow>()
.alias("account")
.leftJoin<CustomerRow>("customer") { account, customer ->
account.customerId == customer.id
}
.select { account, customer ->
AccountSummary(
account.customerId,
customer.name,
sum(account.balance),
count()
)
}
.where { account, customer -> account.balance > minimum }
.groupBy { account, customer -> listOf(account.customerId, customer.name) }
.having { account, customer -> sum(account.balance) > minimum }
.orderByDescending { account, customer -> sum(account.balance) }
.limit(100)
.offset(20)
.build()
```
Join methods include `join`, `leftJoin`, and `rightJoin`. Aggregates include
`count`, `countDistinct`, `sum`, `avg`, `min`, and `max`.
### Inserts and bulk inserts
```kotlin
val insert = sql.insert<AccountRow>(row).build()
val bulk = sql.insertAll<AccountRow>(rows).build()
```
Bulk inserts create runtime-sized PostgreSQL placeholder lists and reject empty
input.
### Conflicts and updates
```kotlin
val upsert = sql.insert<AccountRow>(row)
.onConflict { it.id }
.doUpdate { excluded ->
AccountRow.balance = excluded.balance
}
.build()
val update = sql.update<AccountRow>()
.set { row ->
row.balance = row.balance + amount
row.closedAt = now()
}
.where { it.id == accountId }
.returning { it }
.build()
```
Write targets are checked for existence, mutability, nullability, duplicate
assignment, and value compatibility.
### Execution
```kotlin
val accounts = sql.from<AccountRow>()
.where { it.customerId == customerId }
.fetch(pool, ctx)
.unwrap()
val account = sql.from<AccountRow>()
.where { it.id == accountId }
.single(pool, ctx)
.unwrap()
```
Execution terminals are `fetch`, `single`, and `iterator`. They return typed
`Result` values and use generated row scanners.
## Go interop
Go packages are imported directly:
```kotlin
import http net.http
import pgxpool "github.com/jackc/pgx/v5/pgxpool"
```
Go selectors are written in lower camel case and emitted with exported Go
names. Named arguments construct external Go structs:
```kotlin
val client = http.Client(timeout = 3 * time.second)
```
`go/types` and `go/packages` provide function, method, field, alias, tuple,
variadic, and error-return signatures to semantic analysis and the LSP.
## Build and run
```bash
go run ./cmd/gotlinc build ./examples/hello.gt
./hello
go run ./cmd/gotlinc run ./examples/hello.gt
```
Emit Go source:
```bash
go run ./cmd/gotlinc build -src ./examples/hello.gt -o /tmp/hello.go
```
Emit and consume package interfaces:
```bash
gotlinc build -src \
-metadata-output platform.gti.json \
-metadata-package example/platform \
platform.gt -o platform.go
gotlinc build -src \
-metadata platform.gti.json \
service.gt -o service.go
```
## Language server and editor
Build the language server:
```bash
go build -o ./bin/gotlin-lsp ./cmd/gotlin-lsp
```
The VS Code extension under `tools/vscode-gotlin` provides syntax highlighting,
snippets, diagnostics, hover, document symbols, definitions, references, and a
`gopls` bridge for imported Go APIs.
```bash
cd tools/vscode-gotlin
npm install
npm run check
```