Language Reference
Complete reference for Affogato — a JVM language that transpiles to Java 21. Covers syntax, nullability, Java interop, tooling, and error codes.
On This Page
Getting Started
Prerequisites
- Java 21 SDK or later
- Gradle 8+ (wrapper included in the project)
- IntelliJ IDEA (optional, for plugin support)
Gradle Plugin Setup
Add the Affogato Gradle plugin to your build.gradle.kts:
plugins {
id("dev.affogato") version "0.1.0"
application
}
dependencies {
// Your Java dependencies work seamlessly
implementation("com.google.guava:guava:32.0.0-jre")
} Place your .aff source files in src/main/affogato/. The plugin will automatically discover and compile them, wiring the output into compileJava.
CLI Compiler
Run the compiler directly from the project root:
GRADLE_USER_HOME=.gradle ./gradlew :affogato-compiler:run \
--args="src/main/affogato build/generated/sources/affogato/main/java" Running the Hello Sample
cd affogato-samples/hello
GRADLE_USER_HOME=../../.gradle ../../gradlew run Building the Project
GRADLE_USER_HOME=.gradle ./gradlew build Affogato for Java Developers
Affogato is designed to feel instantly familiar to Java developers while eliminating standard boilerplate. Since it transpiles to Java 21, all your JVM knowledge, libraries, and tools work out of the box.
How Transpilation Works
Every .aff file compiles to .java source before javac runs. The Gradle plugin wires generated sources into compileJava automatically.
describe(value: Object): String { guard value is String else { return "other" } return value as String }
public String describe(Object value) {
if (!(value instanceof String)) {
return "other";
}
return ((String) value);
} class User { let name: String! var count: int init(name: String!, count: int) { this.name = name this.count = count } }
public class User {
public final String name;
public int count;
public User(String name, int count) {
this.name = name;
this.count = count;
}
} func loop() { var names = List<String>() names.add("a") for name in names { println(name) } }
public void loop() {
java.util.ArrayList<String> names =
new java.util.ArrayList<String>();
names.add("a");
for (var name : names) {
System.out.println(name);
}
} class Dog : Animal { override speak(): String { return "Woof!" } }
public class Dog extends Animal {
@Override
public String speak() {
return "Woof!";
}
} Quick Comparison
Here is a side-by-side comparison of common Java and Affogato patterns:
// Java
final String name = "espresso";
int count = 1;
Dog dog = new Dog("Fido");
// Affogato
let name = "espresso" // let = final / immutable
var count = 1 // var = mutable
let dog = Dog(name = "Fido") // no 'new' keyword, named args Key Differences
| Concept | Java | Affogato |
|---|---|---|
| Mutability | Mutable by default. Use final for immutability. | Explicit: var (mutable) vs let (immutable). |
| Instantiation | Requires the new keyword. | new is optional (e.g. User()). |
| Constructors | Same name as the class; verbose field binding. | Compact constructors automatically bind fields, or use init. |
| Null Safety | Optional or annotations (not compiler enforced). | First-class: Type! (non-null) and Type? (nullable). |
| Semicolons | Mandatory at the end of statements. | Completely optional. |
| Inheritance | Uses extends and implements. | Uses a single colon (:) for both. |
Keywords & Syntax
Affogato introduces keywords and syntax that do not exist in Java. Each lowers to standard Java 21 during compilation. See the full keyword reference for detailed examples and generated Java output.
let
Immutable binding — equivalent to Java final.
var
Mutable binding for locals and fields.
func
Shorthand for void return type on methods.
init
Explicit constructor keyword — replaces the Java class-name constructor.
override
Marks a method that overrides a superclass or interface member.
guard
Swift-style early exit when a condition is false.
in
For-in iteration over arrays and Iterable collections.
is
Type test — equivalent to Java instanceof.
as
Cast expression — equivalent to Java (Type) expr.
not
Keyword-style logical negation — equivalent to prefix !.
: inheritance
Single colon replaces extends and implements.
Type? and Type!
Nullable and non-null reference type suffixes.
name: Type
Type-after-name declaration syntax for parameters, locals, and fields.
[T] list shorthand
Square-bracket syntax for java.util.List.
Named arguments
Call-site argument labels: name = value.
Optional new
Constructor calls work with or without the new keyword.
String interpolation
$name and ${expr} inside string literals.
Trailing closures
Block after a call binds to the last parameter.
Syntax Overview
Package & Imports
Package declarations and imports mirror Java syntax. Semicolons are optional everywhere.
package com.example
import java.util.List
import java.util.Map
import static java.util.Collections.sort Variables
Affogato uses var for mutable bindings and let for immutable bindings:
var counter: int = 0 // mutable, explicit type
var name = "Affogato" // mutable, type inferred
let pi = 3.14159 // immutable, type inferred
let max: int = 100 // immutable, explicit type Methods & Functions
Methods can use Java-style or Affogato-style return type annotation. func is an alias for void:
// Java-style: return type first
String greet(String name) {
return "Hello, " + name
}
// Affogato-style: return type after
greet(name: String): String {
return "Hello, " + name
}
// func = void
func logMessage(msg: String) {
println(msg)
} Operators
| Operator | Affogato | Java Equivalent |
|---|---|---|
is | obj is String | obj instanceof String |
as | obj as String | (String) obj |
not(x) | not(flag) | !flag |
: | class A : B | class A extends B |
Classes, Records & Enums
Classes
Classes use familiar syntax with the : operator for inheritance and interface implementation:
class Vehicle {
var brand: String!
var speed: int = 0
init(brand: String!) {
this.brand = brand
}
accelerate(by: int): int {
speed = speed + by
return speed
}
}
class Car : Vehicle {
var doors: int
init(brand: String!, doors: int) {
super(brand)
this.doors = doors
}
override accelerate(by: int): int {
println("Vroom! " + brand)
return super.accelerate(by)
}
} Compact Record Constructors
Records support a compact header syntax that auto-generates accessors:
// Compact form — preferred
record Point(x: int, y: int)
record User(name: String!, email: String?, age: int)
// Usage
let p = Point(x = 10, y = 20)
println(p.x()) // Java record accessor Enums
enum Status {
PENDING, ACTIVE, ARCHIVED
isActive(): boolean {
return this == Status.ACTIVE
}
} Interfaces with Default Methods
interface Greetable {
greet(): String
default greetLoud(): String {
return greet().toUpperCase()
}
}
class FriendlyBot : Greetable {
override greet(): String {
return "Hello from Affogato!"
}
} Annotations
@Deprecated
class OldService {
@SuppressWarnings("unused")
func legacyMethod() {
// ...
}
}
@FunctionalInterface
interface Transformer {
transform(input: String): String
} Nullability
Affogato brings first-class nullability to the JVM without requiring a new runtime.
Type Markers
| Syntax | Meaning | Java output |
|---|---|---|
String! | Non-null reference — compile error if assigned null | @NotNull String |
String? | Nullable reference — may be null | @Nullable String |
String | Platform type — same as Java, no check enforced | String |
Examples
var name: String! = "Affogato" // ok
var alias: String? = null // ok
// Compile error: AFFOGATO_ASSIGNMENT_TYPE
// var bad: String! = null
func processName(n: String!) {
// n is guaranteed non-null here
println(n.length())
}
func processAlias(n: String?) {
// n may be null — check before use
if n != null {
println(n.length())
}
} Unsupported (Production Subset)
The following operators are not in the current production subset and will emit explicit diagnostics:
- AFFOGATO_UNSUPPORTED_SAFE_CALL — Safe call operator ?.
- AFFOGATO_UNSUPPORTED_ELVIS — Elvis operator ?:
- AFFOGATO_UNSUPPORTED_NOT_NULL_ASSERTION — Not-null assertion operator !!
Control Flow
If / Else
Standard if/else with optional parentheses around the condition:
if score > 90 {
println("Excellent!")
} else if score > 70 {
println("Good")
} else {
println("Keep brewing ☕")
} Guard Clause
Swift-inspired early exit — the else block must exit (return, throw, or break). See also guard:
func process(name: String?) {
guard name != null else {
return // early exit
}
// name is safe to use here
println("Processing: " + name)
}
func validateAge(age: int) {
guard age >= 18 else {
throw new IllegalArgumentException("Must be 18+")
}
println("Access granted!")
} For-In Loops
var items: List<String> = List<String>()
items.add("espresso")
items.add("affogato")
items.add("latte")
for item in items {
println(item.toUpperCase())
} While
var count = 0
while count < 5 {
println("Iteration: " + count)
count = count + 1
} Switch Statements & Expressions
// Switch statement
switch day {
case 1 -> println("Monday")
case 5 -> println("Friday")
case 6, 7 -> println("Weekend!")
default -> println("Midweek")
}
// Switch expression (assigns a value)
let label: String = switch status {
case "ACTIVE" -> "✓ Active"
case "PENDING" -> "⏳ Pending"
default -> "○ Unknown"
}
// Block body in switch arm
let result = switch code {
case 200 -> {
println("Success")
"OK"
}
default -> "Error"
} Try / Catch / Finally
try {
var data = riskyOperation()
println(data)
} catch (e: IOException) {
println("IO error: " + e.getMessage())
} catch (e: IllegalArgumentException | NumberFormatException) {
// Multi-catch via pipe
println("Argument error: " + e.getMessage())
} finally {
println("Cleanup done")
} Java Interop
Affogato is designed for seamless Java integration. You can call any Java library from .aff code with no adapters or wrappers needed.
Calling Java Methods
import java.util.ArrayList
import java.time.LocalDate
func demo() {
var list = new ArrayList<String>()
list.add("hello")
list.add("world")
var today = LocalDate.now()
println("Today: " + today.toString())
println("Size: " + list.size())
} Collection Aliases
Affogato provides short-hand aliases for common Java collections:
var strings: List<String> = List<String>() // ArrayList
var numbers: Set<Integer> = Set<Integer>() // HashSet
var map: Map<String, Integer> = Map<String, Integer>() // HashMap Static Imports
import static java.util.Collections.sort
import static java.lang.Math.max
func compute(a: int, b: int): int {
return max(a, b) // calls Math.max directly
}
func sortList(items: List<String>) {
sort(items) // calls Collections.sort
} Lambdas & Method References
import java.util.function.Function
import java.util.stream.Collectors
func transform(items: List<String>): List<String> {
// Lambda expression
return items.stream()
.filter(s -> s.length() > 3)
.map(s -> s.toUpperCase())
.collect(Collectors.toList())
}
func withRef(items: List<String>): List<String> {
// Method reference
return items.stream()
.map(String::trim)
.collect(Collectors.toList())
} Named Arguments for Java
Named arguments work for Java calls when the Java code was compiled with -parameters:
// Positional (always works)
createUser("Alice", 42)
// Named (requires -parameters in Java compilation)
createUser(name = "Alice", age = 42) Generics & Wildcards
func firstElement<T>(list: List<T>): T {
return list.get(0)
}
// Common wildcard cases are supported
func printAll(items: List<? extends Object>) {
for item in items {
println(item.toString())
}
} Overloads & Named Arguments
Overload Resolution Phases
Affogato resolves overloads in three phases, matching Java's JLS approach:
- Strict phase — exact type match, no boxing/unboxing
- Loose phase — with boxing/unboxing and widening
- Varargs phase — varargs expansion
Named Arguments
Name your arguments to improve readability. The compiler reorders them after overload resolution:
func sendEmail(to: String!, subject: String!, body: String?) {
// implementation
}
// Positional call
sendEmail("alice@example.com", "Hello", "Body text")
// Named — order doesn't matter
sendEmail(body = "Body text", to = "alice@example.com", subject = "Hello") Ambiguous Overloads
When no single best candidate can be selected, the compiler fails with a diagnostic rather than producing ambiguous Java:
// Given:
// func process(x: int): void
// func process(x: long): void
// func process(x: double): void
process(42) // resolves to int (strict)
process(42L) // resolves to long (strict)
process(42.0) // resolves to double (strict) Gradle Plugin
Basic Configuration
// build.gradle.kts
plugins {
id("dev.affogato") version "0.1.0"
application
}
application {
mainClass.set("com.example.Main")
} How It Works
- The plugin registers a
compileAffogato<SourceSet>task for each Java source set - Generated Java source is placed in
build/generated/sources/affogato/<sourceSet>/java/ - The generated sources are wired into the matching
compileJava<SourceSet>task - Fully configuration-cache compatible with cacheable task inputs/outputs
Multiple Source Sets
sourceSets {
main {
// Affogato sources: src/main/affogato/
}
test {
// Test Affogato sources: src/test/affogato/
}
integration {
// Integration test sources: src/integration/affogato/
}
} Compiler Options
affogato {
// Compiler configuration (future versions)
} Gradle TestKit Integration
The plugin ships with Gradle TestKit coverage for single-project builds and compiler failure scenarios. Errors produce clear diagnostics and no partial generated Java on failure.
IntelliJ Plugin
The Affogato IntelliJ plugin provides first-class IDE support for .aff files.
Features
- Syntax highlighting — keywords, types, strings, comments with JFlex lexer
- Navigation — Go to Definition for classes, records, enums, interfaces, fields, methods, parameters
- Find Usages — project-wide reference search
- Rename — safe rename refactoring for Affogato declarations
- Compiler Diagnostics — live errors and warnings from the real compiler via AffogatoExternalAnnotator
Installation
Build the plugin from source using the IntelliJ Platform Gradle Plugin:
./gradlew :affogato-intellij-plugin:buildPlugin Then install the generated .zip from affogato-intellij-plugin/build/distributions/ via Settings → Plugins → Install from disk.
Grammar Synchronization
The IntelliJ Grammar-Kit/JFlex grammars (Affogato.bnf + Affogato.flex) are maintained separately from the compiler grammar (Affogato.g4) and must be kept in sync manually when the language syntax changes.
Deferred Features
- Code formatter and brace matcher
- Inspections and quick fixes
- Java-aware imports and code completion
- JetBrains Marketplace publication
Diagnostic Codes
Affogato produces stable, namespaced diagnostic codes. All codes are prefixed with AFFOGATO_.
Stable Error Codes
| Code | Description |
|---|---|
| AFFOGATO_PARSE | Syntax or parser error in the source file |
| AFFOGATO_TYPE_RESOLUTION | Unknown or unresolvable type reference |
| AFFOGATO_CALL_RESOLUTION | Unresolved method or function call |
| AFFOGATO_CONSTRUCTOR_RESOLUTION | Unresolved constructor call |
| AFFOGATO_PROPERTY_RESOLUTION | Unresolved property on a known receiver type |
| AFFOGATO_RETURN_TYPE | Returned expression is incompatible with method return type |
| AFFOGATO_RETURN_FLOW | Non-void method may complete without returning a value |
| AFFOGATO_ASSIGNMENT_TYPE | Initializer or assignment expression is type-incompatible |
| AFFOGATO_CONDITION_TYPE | Condition expression is not boolean |
| AFFOGATO_NAMED_ARGS | Named arguments cannot be mapped to the target callable |
Unsupported Feature Codes
These codes indicate features that are outside the current production subset:
| Code | Feature |
|---|---|
| AFFOGATO_UNSUPPORTED_SAFE_CALL | Safe call operator ?. |
| AFFOGATO_UNSUPPORTED_ELVIS | Elvis operator ?: |
| AFFOGATO_UNSUPPORTED_NOT_NULL_ASSERTION | Not-null assertion operator !! |
Reading Diagnostics
The compiler outputs diagnostics in the following format:
src/main/affogato/Service.aff:14:5: error [AFFOGATO_RETURN_TYPE]
Incompatible return type: expected 'String' but found 'int'
return count
^^^^^