Reference

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.

Guard.aff Affogato
describe(value: Object): String {
  guard value is String else {
    return "other"
  }
  return value as String
}
Guard.java Java 21
public String describe(Object value) {
  if (!(value instanceof String)) {
    return "other";
  }
  return ((String) value);
}

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

ConceptJavaAffogato
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.

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

OperatorAffogatoJava Equivalent
isobj is Stringobj instanceof String
asobj as String(String) obj
not(x)not(flag)!flag
:class A : Bclass 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

SyntaxMeaningJava output
String!Non-null reference — compile error if assigned null@NotNull String
String?Nullable reference — may be null@Nullable String
StringPlatform type — same as Java, no check enforcedString

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:

  1. Strict phase — exact type match, no boxing/unboxing
  2. Loose phase — with boxing/unboxing and widening
  3. 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

CodeDescription
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:

CodeFeature
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
         ^^^^^