Expressions
Partly written. §1 to §6 were written with milestones M4 to M8a of the Lyric 5 plan and the catch-up block N (2026-10-07); lambdas, trailing blocks and
withfollow, spec-first: each rule lands here with its conformance case before or with its implementation. Source of the decisions: 08 Syntax, 04 Abstraction D6.
Scope
Precedence, evaluation order, assignment as an expression, ++/--, wrapping operators, ??, if and match as values, lambdas and trailing blocks, with.
1. Calls
A call names a function and gives it arguments (04 D4, D5, 08 Y9).
Conformance. conformance/cases/08-expressions/.
1.1 Arguments
- An argument is positional, in parameter order, or named,
connect(host: "h", port: 80): the name is the parameter's own, every parameter has one, and a parameter's name is therefore part of the function's public form (D5 F5). Positional arguments come first, named ones after them in any order (F6); a positional argument after a named one, a name given twice or for a parameter already set positionally, and a name no parameter has are refused (LYR-SEM0119). A trailing block (03 §8.2) is a positional argument, the call's last: behind named ones it is refused like any other. A variadic parameter,nums: int..., takes the remaining positional arguments as an array of what it names,int[], and is not named (F8). It stands last and has no default — a call without its arguments passes the empty array. Each remaining argument is one element, an array too:sum(xs)withxs: int[]handssumone argument that is noint(LYR-SEM0001), and where the element type takes an array —Any..., a type parameter'sT...— the array is that one element.xs...spreads an array over the parameter instead: the argument is the rest — the one argument the call spreads, alone in the rest, of the parameter's array type, at a parameter that is variadic (LYR-SEM0166) — and the parameter is that array, nothing is copied. So one variadic function hands its arguments on to another,fn logged(xs: int...): int { return sum(xs...); }. Lyric 4'sparams xs: int[]is gone (LYR-SEM0024). - A parameter may carry a default,
fn connect(host: string, port: int = 80), at any position (F3): a call that leaves it out gets the default, and a default in the middle is left out by naming what follows. The default is evaluated per call, in the function's scope, after the arguments before it, and may read the parameters declared before it —fn span(from: int, to: int = from + 10); a later parameter is not in its scope, and neither isthis(LYR-SEM0120) — the receiver is nobody's before the call (F2). A parameter without a default that a call gives nothing for is refused (LYR-SEM0014). - A call through a function value is positional only: the value's parameters have no names
(
LYR-SEM0119). &xmarks the argument of a place parameter (03 §2.3a): the placex, not its value. The mark covers the whole argument and stands at an argument and nowhere else (LYR-PAR0054).- The order. A call through a receiver evaluates the receiver first, then the arguments (04 D6: left to right everywhere) — for a member a block gives as for a type's own (M8a S15: a block's evaluated its arguments first).
1.2 One name, several counts
- Functions and methods of one scope may share a name when they take different numbers of
arguments —
of(hex)besideof(r, g, b)— and never otherwise (D4): a call counts its arguments and finds one candidate, with no ranking and no conversion of any kind; none that takes that many is refused (LYR-SEM0087). The names of the arguments decide nothing (F7). Members that answer instances of one generic interface are the one exception: they share a count, and the arguments' types choose (05 §3 rule 5). A type's members are one scope with its blocks' (05 §13): a method and a static reached through the type's name count among them alike —int8.parse(s)besideint8.parse(s, 16). - Two declarations of one name whose counts overlap for any number — a default widens a
count to a range — are a redeclaration (
LYR-SEM0085), at the declaration and never at a call. An interface declares a member once (LYR-SEM0088). - The bare name of a shared one is a value only where the position says which, by its
function type (
LYR-SEM0089). Point(1, 2)isPoint.new(1, 2)(Y9):newis an ordinary static function, soPoint(4)reaches a one-argument factory declared beside the two-argument one.
2. if and match as values
if (c) a else bis an expression:cis abool, theelseis required (LYR-PAR0036), and exactly one branch is evaluated. A branch is an expression or a value block (07 §3):if (c) { …; a } else { …; b }; the two forms mix, andelse ifgoes on. A block ends its branch — an operator behind it takes the wholeif,if (c) { 1 } else { 2 } + 3is a sum — where a branch without braces takes everything to its right.- Its type. Where the position wants a type, each branch meets it and the
ifhas it; otherwise the branches unify, as arms do (LYR-SEM0016), anullbranch making the other optional. A branch worthnever— one that leaves — contributes nothing:if (x < 0) { continue; } else { x }isx's type. Both branches worthnever: so is theif. Both worth nothing — two calls that give none, two blocks without a tail — and so is theif: it stands where nobody takes a value,(c) => if (c) yes() else no(). x ?? { … }: the right of??may be a value block, evaluated only whenxis absent —let n = v ?? { return 0; };,v ?? { let d = base(); d + 1 }.match (e) { p => v, p => { …; v } }is worth the arm that is taken. An arm's body is an expression or a value block; the arms unify as the branches of anifdo, or each meets the type the position wants. A block arm that leaves —return,throw, and abreakorcontinueof the loop around thematch— contributes nothing; amatchwhose arms all leave is worthnever, and one whose arms are all worth nothing is worth nothing. A block arm that runs to its end without a tail beside an arm that gives a value, or where the position wants one, is refused (LYR-SEM0033).- Definite assignment (07 §1 rule 5) treats the
branches of an
ifexpression as anifstatement's: behind it a variable is assigned when every branch that completes assigned it. comptime if (c) a else bis a value whose condition is decided while compiling, the arms expressions (11 §4 rule 9): where a round decidesc, the chosen arm alone is checked; otherwise both arms are, of one type, and the site chooses.
3. Assignment
An assignment is an expression (04 D6: left to right everywhere, the target before the value).
- The target before the value. What leads to the place — the receiver, then the index or
the key — is evaluated first, left to right and once; then the value; then the store.
xs[next()] = next()stores the second count at the first — in an array as through a type's own index (05 §12 rule 9). - A compound assignment
t op= v, andt++andt--, evaluate what leads to the place once, read it, compute, and write it:xs[next()] += 1callsnextonce. - Its value is the value stored, at the type of the place:
o = vwhereoholds a?Tor an interface is worth that?Tor that interface. A compound assignment is worth the new value;t++the old one,++tthe new. - An assignment written as the condition of an
if, awhileor ado … whileis warned about (LYR-SEM0202): a==is what is usually meant. A second pair of parentheses says the assignment is meant —if ((done = check()))— and nothing is warned.
4. Precedence
The operators bind by these levels, the tightest first (08 Y4):
Level Operators Associativity postfix .?.[ … ]( … )!++--with { … }left prefix !-~++--&trytry?try!throwcomptimeright A
!directly behind a name, before(or{, is no postfix: it is a macro's site,name!(args)(11 §5);x! in xsandx!.f()unwrap as ever.quote { … }is a primary expression, a tree as a value, and#{e}inside it a splice (11 §5 rules 6, 7);#stands nowhere else. | cast |as| left | | multiplicative |*/%*%| left | | additive |+-+%-%| left | | shift |<<>>| left | | range |....=| none | | bitwise and |&| left | | bitwise xor |^| left | | bitwise or |\|| left | | comparison |<<=>>=isin!in| none | | equality |==!=| none | | logical and |&&| left | | logical or |\|\|| left | | coalescing |??| right | | assignment |=and every compound assignment | right |So
1 << 2 + 1is1 << 3,x as int * 2casts before it multiplies, and0..n + 1ends atn + 1.A level without associativity does not chain:
a..b..cis refused (LYR-PAR0005), and so area < b < canda == b == c(LYR-PAR0059) — a chained comparison would compare aboolwith the third operand, a chained equality would take aboolfor what was meant. Parentheses say which:(a == b) == c. (Informative.) The diagnostic names the form likely meant,a < b && b < c.trycovers everything to its right in the expression it stands in:try a + bistry (a + b)(06 §3).
5. Evaluation order
- Left to right, everywhere (04 D6): the left operand of an operator before the right one; a call's receiver before its arguments, and the arguments in the order they are written; the parts of a literal, a tuple and an initializer in the order they stand. An assignment evaluates its target before its value (§3 rule 1).
a && bevaluatesbonly whenaistrue,a || bonly whenaisfalse,a ?? bonly whenaisnull(§2 rule 3).
6. ++ and --
++and--add and take one on a place that holds a number — avar, a field, an element:x++,p.count--,++xs[i]. A place that cannot be written is refused (LYR-SEM0019). The step is checked as+and-are: past the type's range it panics (LYR-RT0002, 03 §1).x++andx--are worth the old value,++xand--xthe new one (§3 rule 3); what leads to the place is evaluated once (§3 rule 2).