Reference code clinic — follow 6 × 7
Run the reference executable trace first. This clinic follows its
smallest interesting expression—6 * 7—through four implementation decisions.
You are not trying to learn every surrounding type. At each stop, ask:
- What enters?
- What fact is added?
- Which next stage consumes it?
Hold the three visible forms together
Section titled “Hold the three visible forms together”def answer(): Integer return 6 * 7endfunc Answer() int { return trbIntegerMultiply_7a501f0387c1974d(6, 7)}answer=42The generated helper name contains an implementation-owned suffix. Do not
search for that exact suffix in source. Follow the stable ideas instead:
binary expression, checked Integer multiplication, and Go generation.
1. Parsing records the authored operation
Section titled “1. Parsing records the authored operation”The expression parser gives * a higher precedence than + and -:
var precedences = map[string]int{ // lower-precedence operators omitted "+": 10, "-": 10, "*": 11, "/": 11, "%": 11, "**": highestBinaryPrecedence,}Read the exact table in
internal/parser/expression.go.
After parsing the right operand, the same function preserves the operator and
both operands in an ast.BinaryExpression:
prec, ok := precedences[tok.Lexeme]// stop or recursively parse the right operandif tok.Lexeme == ".." || tok.Lexeme == "..." { // build a range} else { left = &ast.BinaryExpression{Left: left, Operator: tok.Lexeme, Right: right}}The complete branch also carries the combined source span. The parser has answered “what was written?” It has not yet proved that multiplication is valid for these values.
Small Go note: map[string]int maps operator spellings to integer
precedence. left = &ast.BinaryExpression{...} creates a syntax node and keeps
a pointer to it. You do not need deeper Go knowledge to follow this handoff.
2. Checking gives the operation a type
Section titled “2. Checking gives the operation a type”The checker recognizes the syntax node in a Go type switch, recursively checks both operands, and delegates the operator rule:
case *ast.BinaryExpression: left := c.checkExpression(n.Left, sc) right := c.checkExpression(n.Right, sc) typ = c.checkBinaryOperator(n.Span(), n.Operator, left, right)Open the
BinaryExpression checker branch
to see value checks and numeric conversions around those essential lines.
The operator rule says that -, *, /, and ** accept two non-nullable
numbers and return their common numeric type:
case "-", "*", "/", "**": if isNonNullableNumber(left) && isNonNullableNumber(right) { return commonNumberType(left, right) }That rule is part of
checkBinaryOperator.
For 6 * 7, both operands and the result are Integer.
Small Go note: case *ast.BinaryExpression asks whether an interface value
contains that concrete syntax-node type. It is a dispatch point, not an extra
compiler phase.
3. Lowering carries checked meaning into IR
Section titled “3. Lowering carries checked meaning into IR”Lowering converts the syntax node to the backend-neutral typed IR:
case *ast.BinaryExpression: return &ir.Binary{ExprBase: base, Left: l.expression(n.Left), Operator: n.Operator, Right: l.expression(n.Right)}The exact lowering is only
two lines.
The resulting
ir.Binary
keeps the typed base, left operand, operator, and right operand.
This short handoff matters. Go, Ruby, and TypeScript generators can consume one
checked operation instead of rediscovering whether * was legal.
4. The Go generator preserves portable Integer behavior
Section titled “4. The Go generator preserves portable Integer behavior”The Go generator sees an ir.Binary. When its checked result type is Integer
and the operator needs range checking, it chooses a runtime helper:
if n.ExprType().Kind == types.Int && isCheckedIntegerOperator(op) { return g.checkedIntegerBinary(op, left, right)}Read the surrounding
ir.Binary generator branch.
The helper selector maps * to Multiply and marks the checked-Integer runtime
as required:
g.checkedInteger = truename := map[string]string{ "+": "Add", "-": "Subtract", "*": "Multiply",}[operator]return g.checkedIntegerRuntimeName(name) + "(" + left + ", " + right + ")"The
checkedIntegerBinary implementation
is small because runtime support owns the overflow checks. That is why the
generated result is a helper call rather than plain 6 * 7: portable TypeRB
Integer behavior must not depend on a target’s unchecked overflow behavior.
The complete responsibility chain
Section titled “The complete responsibility chain”| Boundary | Added fact | Next consumer |
|---|---|---|
| Parser | 6, *, and 7 form one binary syntax node with a span |
Resolver and checker |
| Checker | both operands and the result are valid Integer values |
Lowering |
| Lowering | one typed, backend-neutral binary operation | All target generators |
| Go generator | multiplication uses portable checked-Integer support | Go toolchain and runtime |
| Program | the operation contributes 42 to visible output |
User and runtime tests |
Run focused evidence
Section titled “Run focused evidence”From the matching type-rb checkout:
go test ./internal/parsergo test ./internal/compilergo test ./internal/cli -run '^TestRunPortableIntegerAndIEEEFloatArithmeticAcrossBackends$'The first command protects source shape. The second crosses checking, IR, and generation boundaries. The named CLI test exercises portable arithmetic across available backends. Choose only the earliest relevant command while editing, then expand verification before review.
Check yourself
Section titled “Check yourself”- Which stage first decides that
*is legal for twoIntegeroperands? - Why should the Go generator consume
ir.Binaryinstead of the parser node? - Why is a checked multiplication helper visible even for the small values
6and7?
Check your answers
- The checker. The parser records the authored operator but does not approve its operand types.
- Typed IR carries the already-checked portable meaning and keeps target generators from reimplementing source-language rules.
- The generator applies one portable
Integerstrategy consistently. The helper also protects other runtime values that may exceed the portable range.
Continue with the workflow and test matrix when you are ready to choose evidence for a change. Use the change journey to decide how far that change must travel, and the big map only when you need more source addresses.