Expressions and type inference
Raven can determine a type from an expression itself, from the place where its value is used, or from both directions at once. This keeps code concise when the type is already clear while preserving static type checking.
let count = 42 // inferred from the initializer
let status: Status = .Ready // inferred from the target
let point: Point = .(2, -1) // constructs the target type
Target typing
Many expressions rely on the type expected by their context, called the target type.
For example, the enum shorthand .B in var grade: Grades = .B uses the declared type Grades to resolve the member.
The constructor shorthand .(...) also requires a target type. The omitted
member name means "construct the target type", so let p: Point = .(2, -1)
binds as Point(2, -1). The same form works in other target-typed contexts
such as constructor arguments and collection elements.
Unit-valued union cases
When a discriminated union case carries exactly one payload of type unit, Raven permits the case to be written without an argument list in expression position. In such contexts, a bare case name is sugar for supplying the sole unit value ().
func Save() -> Result<(), Error> {
return Ok // sugar for `Ok(())`
}
The target-typed member form .Ok is also valid when the target type is known.
This rule applies uniformly to all discriminated unions, not only Result or Option. The case must declare exactly one constructor parameter whose type is unit; cases with additional parameters or non-unit payloads still require an explicit argument list.
This mirrors the pattern-matching rule where a bare case such as Ok matches
Ok(()) when the payload is unit, with .Ok remaining available as the
target-typed shorthand.
Type inference
When an expression or declaration omits an explicit type, Raven infers one from
the expression. If multiple different types can flow to a location—through
conditional branches or early return statements—the inferred result is the
nearest compatible type for the flow.
let pet = if flag { Dog() } else { Cat() }
// pet has an inferred compatible type
Literal expressions infer their ordinary primitive type when used to initialize
let or var bindings. A literal such as 1 can also undergo an implicit
constant conversion when a compatible target type requires it.
var i = 0 // i : int
let j = 0 // j : int
Control-flow expressions participate in the same inference. An if expression
whose branches produce different types finds the nearest compatible type for
those results. Literal branches use or widen from their primitive types as
needed.
Branch type inference
let x: int = 3
let value = if x > 2 { 42 } else { x }
// value : int
let other: long = 0
let widened = if x > 2 { other } else { 42 }
// widened has an inferred compatible numeric type
Each branch contributes its inferred type. Literal branches widen as needed to produce a compatible inferred result type.
Numeric literals choose an underlying primitive type according to their form and optional suffix. The default rules are designed to be predictable while still allowing safe narrowing through implicit constant conversions.
Integer literals
- Unsuffixed integer literals default to
int.- If the value does not fit in
int, the literal is typed aslong.
- If the value does not fit in
- Base prefixes:
0b/0B— binary integer literal0x/0X— hexadecimal integer literal
- The following suffixes override the default:
b/B—bytel/L—long
Unsuffixed integer literals may still convert implicitly to smaller integral
types (such as byte or char) when used as constant expressions and the
value fits in the target type. This mirrors C#’s constant conversion rules and
avoids accidental narrowing for non-constant values.
let a = 42 // int
let b = 4_000_000_000 // long
let x: byte = 12 // OK: constant int fits in byte
let y: byte = 300 // error: constant out of range
let z = 32b // explicit byte literal
let n = 10L // explicit long literal
let bits = 0b1010_0101 // binary int literal
let mask = 0xFF // hexadecimal int literal
Floating-point literals
- Unsuffixed floating-point literals (those containing a decimal point or
exponent) default to
double. - The following suffixes override the default:
f/F—floatd/D—doublem/M—decimal
let d = 3.14 // double
let f = 3.14f // float
let m = 9.99m // decimal
let e = 1e3 // double
Decimal literals do not support exponent notation. Attempting to combine an
exponent with the m/M suffix produces a diagnostic.
Literal values participate in overload resolution and type inference using their underlying primitive type. When a literal appears in a context that supplies a target type, the compiler may apply implicit constant conversions before considering explicit casts.
Overload resolution applies the same rule: a literal argument converts to its
underlying type when selecting among method overloads. For example,
Console.WriteLine(1) binds to Console.WriteLine(int) if such an overload
exists, and Console.WriteLine("test") chooses Console.WriteLine(string).
Function-expression inference
Named functions and methods without an annotated return type default to unit; the
declaration return type is not inferred from body expressions.
Lambdas without an annotated return type infer their result by collecting:
- the types of all explicit
returnstatements, and - the final expression of the outer body when that body has a value-producing tail expression.
Expression statements in nested statement blocks (for example, inside if/while/for
statement bodies) do not participate in lambda return-type inference. If no value-returning
path exists, the lambda return type defaults to unit.
let example = (x: int) -> {
if x > 0 { return x }
"neg"
}
// inferred return type is context-dependent
When a lambda expression is assigned to a binding without an explicit type, Raven
still materialises a concrete delegate. The compiler synthesises an appropriate
System.Func/System.Action definition using the lambda's parameter types and
the inferred return type (treating unit results as actions). Captured
variables participate in the enclosing flow analysis before the delegate type is
constructed, so the lambda observes the same declared type as any other use of
the variable.
let a = 42
let makeAdder = () => a + 3
makeAdder() // returns 45, makeAdder : System.Func<int>
Async function expressions mirror async functions: placing async before the parameter
clause (or using async func) permits await inside the body. When the function expression return type is not annotated
and no delegate supplies one, the compiler wraps the inferred result in
System.Threading.Tasks.Task<T> (or Task when the body produces unit). A delegate
annotation or target type may still specify a concrete Task shape, in which case the
function-expression body must evaluate to the awaited result type rather than the task itself.
Annotating an async function expression with a non-Task return type is an error.
Additional inference rules
The following clarifications extend the type inference model:
- Contextual inference: Raven computes a contextual type based on both expression shape and target type. Inference is bidirectional.
- Literal arithmetic: Non-constant operations use the literals' primitive types unless constant-folded.
- Generic inference: Type argument inference requires a single consistent set of type arguments that satisfies all constraints.
- Nullability:
T?remains the static type of nullable storage. Safe navigation and explicit patterns produce separate result types or bindings; direct null checks do not refine storage. - Pattern binding: See Pattern matching for how
is,if let, andmatchintroduce values whose types are established by a successful pattern. - Tuples: Tuple element names do not affect type identity.
- Ref/out parameters:
refrequires exact type match;outcontributes to inference of the parameter type. - Type stability: Variable declarations have a fixed static type. A successful pattern may introduce a separate binding, but it does not change the type of the matched variable.
- Diagnostics: When conversion fails, diagnostics should identify the conflicting conversions and why. For overloads, diagnostics should explain alternative selections.