package sema

import (
	"fmt"
	"math/big"
	"strconv"
	"strings"

	"github.com/marzeq/qk/attributes"
	"github.com/marzeq/qk/parser"
	"github.com/marzeq/qk/shared"
	"github.com/marzeq/qk/symbols"
	"github.com/marzeq/qk/tokeniser"
	"github.com/marzeq/qk/types"
)

type Validator struct {
	analyser                      *Analyser
	currentFunction               *symbols.Symbol
	currentFunctionParameters     map[*symbols.Symbol]int
	currentTypedVariadicParameter *symbols.Symbol
	loopDepth                     int
	errors                        []error
	warnings                      []error
	validatingTemplate            bool
}

func (a *Analyser) NewValidator() *Validator {
	return &Validator{analyser: a}
}

func (v *Validator) errorf(node parser.Node, format string, args ...any) {
	v.errors = append(v.errors, shared.NewError(node.GetLoc(), format, args...))
}

func (v *Validator) warnLocf(kind shared.WarningKind, loc shared.Location, format string, args ...any) {
	v.warnings = append(v.warnings, shared.NewWarning(kind, loc, format, args...))
}

func (v *Validator) warnIfUnused(symbol *symbols.Symbol, loc shared.Location, warningKind shared.WarningKind, bindingKind string) {
	if v.analyser.currentTrustedStandardLibrary || symbol == nil || symbol.Name == "_" || symbol.Referenced {
		return
	}
	v.warnLocf(warningKind, loc, "unused %s %q", bindingKind, symbol.Name)
}

func (v *Validator) ValidateModule(root *parser.RootNode) {
	v.validatingTemplate = false
	v.selectModule(root)
	v.validateNode(root)
}

func (v *Validator) ValidateGenericTemplates(root *parser.RootNode) {
	v.validatingTemplate = true
	v.selectModule(root)
	for _, node := range root.Body {
		function, ok := node.(*parser.FunctionDefNode)
		if !ok || !function.IsGeneric() {
			continue
		}
		bindings := v.analyser.templateBindings(function.Symbol)
		v.analyser.withDefinitionContext(
			v.analyser.currentMod,
			v.analyser.currentTrustedStandardLibrary,
			bindings,
			func() { v.validateNode(function) },
		)
	}
	v.validatingTemplate = false
}

func (v *Validator) selectModule(root *parser.RootNode) {
	path := v.analyser.modulePaths[root]
	v.analyser.currentMod = path
	if mod := v.analyser.modules[path]; mod != nil {
		v.analyser.current = mod.Scope
		v.analyser.currentTrustedStandardLibrary = mod.TrustedStandardLibrary
	}
	v.analyser.currentImports = v.analyser.importsByModule[path]
	v.analyser.currentImportAliases = v.analyser.importAliasesByModule[path]
	v.analyser.aliases = v.analyser.aliasesByModule[path]
}

func (v *Validator) Errors() []error {
	return v.errors
}

func (v *Validator) Warnings() []error {
	return v.warnings
}

func (v *Validator) validateNode(node parser.Node) {
	switch n := node.(type) {

	case *parser.RootNode:
		for _, stmt := range n.Body {
			v.validateNode(stmt)
		}

	case *parser.ImportNode:

	case *parser.ModuleNode:
		v.validateAttributes(n, n.Attributes, "module", attributes.AttributeTypeLink)

	case *parser.FunctionDefNode:
		if n.GenericInstance || (n.IsGeneric() && !v.validatingTemplate) {
			return
		}
		v.validateAttributes(n, n.Attributes, "function",
			attributes.AttributeTypeInline,
			attributes.AttributeTypeNoInline,
			attributes.AttributeTypeNoReturn,
			attributes.AttributeTypeForeign,
			attributes.AttributeTypeExport,
		)
		if n.Attributes.Get(attributes.AttributeTypeForeign) != nil &&
			n.Attributes.Get(attributes.AttributeTypeExport) != nil {
			v.errorf(n, "function cannot be both foreign and exported")
		}

		prev := v.currentFunction
		prevFunctionParameters := v.currentFunctionParameters
		prevTypedVariadicParameter := v.currentTypedVariadicParameter
		prevLoopDepth := v.loopDepth
		v.currentFunction = n.Symbol
		v.loopDepth = 0
		v.currentFunctionParameters = make(map[*symbols.Symbol]int, len(n.Args))
		for i, argument := range n.Args {
			v.currentFunctionParameters[argument.Symbol] = i
		}
		v.currentTypedVariadicParameter = nil
		if n.Symbol.Signature.TypedVariadic {
			v.currentTypedVariadicParameter = n.Args[len(n.Args)-1].Symbol
		}

		foreign, isForeign := n.Symbol.Attributes.Get(attributes.AttributeTypeForeign).(attributes.FunctionAttributeForeign)
		exported, isExported := n.Symbol.Attributes.Get(attributes.AttributeTypeExport).(attributes.FunctionAttributeExport)
		usesCABI := (isForeign && foreign.ABI == attributes.ForeignABIC) ||
			(isExported && exported.ABI == attributes.ForeignABIC)
		usesQKABI := !usesCABI
		if n.Symbol.Signature.Variadic && usesQKABI {
			v.errorf(n, "functions using the qk ABI cannot be variadic")
		}
		if n.Symbol.Signature.TypedVariadic && usesCABI {
			v.errorf(n, "typed variadic functions cannot use the c ABI")
		}

		seenDefault := false
		for i, param := range n.Symbol.Signature.Parameters {
			parameterNode := parser.Node(n.Args[i])
			if n.Args[i].Type != nil {
				parameterNode = n.Args[i].Type
			}
			if types.HasError(param) {
				if n.Args[i].Default != nil {
					v.validateExpr(n.Args[i].Default)
				}
				continue
			}
			if hasVoidValue(param) {
				v.errorf(parameterNode, "function parameter cannot have type void")
			}
			if usesCABI && types.HasTraitPointer(param) {
				v.errorf(n.Args[i].Type, "trait pointers cannot cross the c ABI")
			}
			if usesCABI && types.HasTaggedUnion(param) {
				v.errorf(n.Args[i].Type, "tagged unions cannot cross the c ABI; expose an explicitly tagged union through @reprof(...) instead")
			}
			if _, array := types.Underlying(param).(types.ArrayType); usesCABI && array {
				v.errorf(n.Args[i].Type, "arrays cannot be direct c ABI parameters; pass a pointer or wrap the array in a struct")
			}
			if !types.IsComplete(param) {
				v.errorf(parameterNode, "function parameter cannot have incomplete type %v", param)
			}
			arg := n.Args[i]
			if n.Symbol.Signature.TypedVariadic && arg.Default != nil {
				v.errorf(arg.Default, "typed variadic functions cannot currently use default parameters")
			}
			if arg.Default == nil {
				if seenDefault {
					v.errorf(arg, "parameter without a default cannot follow a parameter with a default")
				}
				continue
			}
			seenDefault = true
			if usesCABI {
				v.errorf(arg.Default, "functions using the c ABI cannot have default parameters")
			}
			arg.Default = v.validateExprWithExpected(arg.Default, param)
		}
		if ret := n.Symbol.Signature.ReturnType; ret != nil && types.HasError(ret) {
			// The type resolver already reported the primary error.
		} else if ret != nil && !types.IsComplete(ret) {
			v.errorf(n, "function return cannot have incomplete type %v", ret)
		} else if usesCABI && ret != nil && types.HasTraitPointer(ret) {
			v.errorf(n, "trait pointers cannot cross the c ABI")
		} else if usesCABI && ret != nil && types.HasTaggedUnion(ret) {
			v.errorf(n, "tagged unions cannot cross the c ABI; expose an explicitly tagged union through @reprof(...) instead")
		} else if _, array := types.Underlying(ret).(types.ArrayType); usesCABI && ret != nil && array {
			v.errorf(n, "arrays cannot be returned directly through the c ABI; return a struct containing the array")
		}
		returnTypeErroneous := types.HasError(n.Symbol.Signature.ReturnType)
		if ret := n.Symbol.Signature.ReturnType; ret != nil && !returnTypeErroneous && !ret.Equals(types.PrimitiveVoid) {
			if _, multiple := ret.(types.MultipleReturnType); !multiple && hasVoidValue(ret) {
				v.errorf(n, "function return cannot have void as a value type")
			}
		}
		if multiple, ok := n.Symbol.Signature.ReturnType.(types.MultipleReturnType); ok && !returnTypeErroneous {
			if usesCABI {
				v.errorf(n, "multiple return values are not supported by the c ABI; use abi \"qk\"")
			}
			for _, result := range multiple.Types {
				if hasVoidValue(result) {
					v.errorf(n, "multiple return values cannot contain void")
					break
				}
			}
		}

		if n.Body != nil {
			if n.ExpressionBody {
				body := n.Body.(parser.ExpressionNode)
				if parser.NodeFallsThrough(n.Body) {
					n.Body = v.validateExprWithExpected(body, n.Symbol.Signature.ReturnType)
				} else {
					v.validateExpr(body)
				}
			} else {
				v.validateNode(n.Body)
				returnType := n.Symbol.Signature.ReturnType
				invalidReturnType := types.HasError(returnType)
				if returnType != nil &&
					!returnType.Equals(types.PrimitiveVoid) &&
					!invalidReturnType &&
					parser.NodeFallsThrough(n.Body) {
					v.errorf(n.Body, "non-void function may fall through without returning a value")
				}
			}
		}
		if n.Body != nil {
			for i, arg := range n.Args {
				if n.Symbol.Method && !n.Symbol.StaticMethod && i == 0 && arg.Name == "self" {
					continue
				}
				v.warnIfUnused(arg.Symbol, arg.GetLoc(), shared.WarningUnusedParameter, "function parameter")
			}
		}

		v.currentFunction = prev
		v.currentFunctionParameters = prevFunctionParameters
		v.currentTypedVariadicParameter = prevTypedVariadicParameter
		v.loopDepth = prevLoopDepth

	case *parser.BlockNode:
		for _, stmt := range n.Body {
			v.validateStatement(stmt)
		}

	case *parser.DeclarationNode:
		if len(n.GenericParameters) != 0 {
			return
		}
		if n.Name == "_" {
			if n.Value != nil {
				v.validateValueExpr(n.Value)
			}
			break
		}
		v.validateAttributes(n, n.Attributes, "declaration", attributes.AttributeTypeForeign)
		v.finaliseDeclaration(n)
		if n.Symbol.Type != nil && !types.HasError(n.Symbol.Type) && !types.IsComplete(n.Symbol.Type) {
			v.errorf(n, "cannot declare a value of incomplete type %v", n.Symbol.Type)
		}
		if n.Attributes.Get(attributes.AttributeTypeForeign) != nil && !types.HasError(n.Symbol.Type) && types.HasTaggedUnion(n.Symbol.Type) {
			v.errorf(n, "tagged unions cannot cross a foreign boundary; expose an explicitly tagged union through @reprof(...) instead")
		}
		if v.currentFunction != nil {
			v.warnIfUnused(n.Symbol, n.NameLoc, shared.WarningUnusedVariable, "variable")
		}
	case *parser.MultiDeclarationNode:
		v.validateMultiDeclaration(n)
		for i, symbol := range n.Symbols {
			v.warnIfUnused(symbol, n.NameLocs[i], shared.WarningUnusedVariable, "variable")
		}

	case *parser.AssignmentNode:
		v.validateAssignment(n)

	case *parser.IfNode:
		v.validateIf(n)

	case *parser.MatchNode:
		v.validateMatch(n, nil)

	case *parser.ForNode:
		v.validateFor(n)

	case *parser.RangeForNode:
		v.validateRangeFor(n)

	case *parser.ForEachNode:
		v.validateForEach(n)

	case *parser.ControlKeywordNode:
		if n.Keyword == tokeniser.KeywordBreak || n.Keyword == tokeniser.KeywordContinue {
			if v.loopDepth == 0 {
				v.errorf(n, "%s is only valid inside a loop", n.Keyword)
			}
		} else if n.ReturnValue != nil {
			v.validateReturn(n)
		}

	case *parser.DeferNode:
		v.validateNode(n.Action)

	case parser.ExpressionNode:
		v.validateExpr(n)

	case *parser.TypeAliasNode:
		if len(n.GenericParameters) != 0 {
			return
		}
		if types.HasError(n.Symbol.TypeInfo) {
			return
		}
		underlying := types.Underlying(n.Symbol.TypeInfo)
		if n.Transparent && types.IsOpaque(n.Symbol.TypeInfo) {
			v.errorf(n, "opaque type %q cannot be a transparent alias", n.Name)
		} else if _, trait := underlying.(types.TraitType); trait {
			if n.Transparent {
				v.errorf(n, "trait %q cannot be a transparent alias", n.Name)
			}
		} else if _, opaque := underlying.(types.OpaqueType); !opaque && !types.IsComplete(underlying) {
			v.errorf(n, "type %q contains an incomplete type by value", n.Name)
		} else {
			switch underlying.(type) {
			case types.StructType, types.UnionType, types.SliceType, types.ArrayType:
				if hasVoidValue(underlying) {
					v.errorf(n, "type %q contains void by value", n.Name)
				}
			}
		}

	default:
		panic(fmt.Sprintf("unhandled node type %T", n))
	}
}

func (v *Validator) validateStatement(node parser.Node) {
	v.validateNode(node)
	if expression, ok := node.(parser.ExpressionNode); ok && types.HasError(expression.GetType()) {
		return
	}

	switch n := node.(type) {
	case *parser.FunctionCallNode:
		return
	case *parser.InlineAsmNode:
		return
	case *parser.BlockNode:
		if !n.Expression {
			return
		}
	case *parser.IfNode:
		if !n.Expression {
			return
		}
	case *parser.MatchNode:
		if !n.Expression {
			return
		}
	case parser.ExpressionNode:
		// Any other expression in statement position computes a value that is
		// immediately discarded. Require that intent to be explicit.
	default:
		return
	}

	v.errorf(node, "expression result is unused; assign it to '_' to discard it")
}

func (v *Validator) validateAttributes(node parser.Node, attrs attributes.Attributes, entity string, allowed ...attributes.AttributeType) {
	for _, attr := range attrs {
		valid := false
		for _, attrType := range allowed {
			if attr.GetType() == attrType {
				valid = true
				break
			}
		}
		if !valid {
			v.errorf(node, "@%s attribute does not apply to %ss", attr.GetType(), entity)
		}
	}
}

func (v *Validator) finaliseDeclaration(n *parser.DeclarationNode) {
	if n.Value == nil {
		return
	}
	if noInit, ok := n.Value.(*parser.NoInitializerNode); ok {
		if n.TypeNode == nil {
			v.errorf(n, "'---' declaration requires a type annotation")
			n.Symbol.Type = types.ErrorType{}
			return
		}
		declared := n.Symbol.Type
		if declared == nil {
			declared = v.analyser.resolveTypeNode(n.TypeNode)
		}
		noInit.SetType(declared)
		n.Symbol.Type = declared
		return
	}

	if n.TypeNode != nil {
		declared := n.Symbol.Type
		if declared == nil {
			declared = v.analyser.resolveTypeNode(n.TypeNode)
		}
		if types.HasError(declared) {
			v.validateExpr(n.Value)
			n.Symbol.Type = types.ErrorType{}
			return
		}
		if hasVoidValue(declared) {
			v.errorf(n, "declaration cannot have type void")
			v.validateExpr(n.Value)
			n.Symbol.Type = types.ErrorType{}
			return
		}
		n.Value = v.validateExprWithExpected(n.Value, declared)

		n.Symbol.Type = declared
		return
	}

	if !v.validateValueExpr(n.Value) {
		n.Symbol.Type = types.ErrorType{}
		return
	}

	valueType := n.Value.GetType()
	if _, multiple := valueType.(types.MultipleReturnType); multiple {
		v.errorf(n, "multiple return values must be unpacked into a matching target list")
		n.Symbol.Type = types.ErrorType{}
		return
	}

	if types.HasUntyped(valueType) {
		if n.Symbol.InlineComptime {
			return
		}
		if _, ok := valueType.(types.UnresolvedEnum); ok {
			v.errorf(n, "cannot infer enum type for declaration; add a type annotation")
		} else {
			v.errorf(n, "cannot infer declaration type from untyped numeric value; add a type annotation or cast")
		}
		n.Symbol.Type = types.ErrorType{}
		return
	}

	n.Symbol.Type = valueType
}

func (v *Validator) validateAssignment(n *parser.AssignmentNode) {
	if len(n.Assignees) > 0 {
		v.validateValueExpr(n.Value)
		if types.HasError(n.Value.GetType()) {
			return
		}
		result, ok := n.Value.GetType().(types.MultipleReturnType)
		if !ok {
			v.errorf(n, "multiple assignment requires a multiple-result expression")
			return
		}
		if len(result.Types) != len(n.Assignees) {
			v.errorf(n, "assignment has %d targets but call returns %d values", len(n.Assignees), len(result.Types))
			return
		}
		for i, target := range n.Assignees {
			if id, ok := target.(*parser.IdentifierNode); ok && id.Name == "_" {
				continue
			}
			if !v.validateLValue(target) {
				continue
			}
			if !result.Types[i].CanCoerceTo(target.GetType()) {
				v.errorf(target, "cannot assign %v to %v", result.Types[i], target.GetType())
			}
		}
		return
	}
	if id, ok := n.Assignee.(*parser.IdentifierNode); ok && id.Name == "_" {
		v.validateValueExpr(n.Value)
		return
	}
	if field, ok := n.Assignee.(*parser.FieldAccessNode); ok && field.IsFlagTest {
		if !v.validateLValue(field.Subject) {
			return
		}
		n.Value = v.validateExprWithExpected(n.Value, types.PrimitiveBool)
		return
	}
	if !v.validateLValue(n.Assignee) {
		return
	}

	v.validateExpr(n.Assignee)

	lhsType := n.Assignee.GetType()
	if types.HasError(lhsType) {
		v.validateExpr(n.Value)
		return
	}
	n.Value = v.validateExprWithExpected(n.Value, lhsType)
}

func (v *Validator) validateMultiDeclaration(n *parser.MultiDeclarationNode) {
	v.validateValueExpr(n.Value)
	if types.HasError(n.Value.GetType()) {
		for _, symbol := range n.Symbols {
			if symbol != nil {
				symbol.Type = types.ErrorType{}
			}
		}
		return
	}
	result, ok := n.Value.GetType().(types.MultipleReturnType)
	if !ok {
		v.errorf(n, "multiple declaration requires a multiple-result expression")
		return
	}
	if len(result.Types) != len(n.Names) {
		v.errorf(n, "declaration has %d names but call returns %d values", len(n.Names), len(result.Types))
		return
	}
	for i, sym := range n.Symbols {
		if sym != nil {
			sym.Type = result.Types[i]
		}
	}
}

func (v *Validator) validateLValue(expr parser.ExpressionNode) bool {
	return v.validateMutablePlace(expr, false)
}

func (v *Validator) validateMutablePlace(expr parser.ExpressionNode, reference bool) bool {
	switch e := expr.(type) {
	case *parser.IdentifierNode:
		v.validateExpr(e)
		if types.HasError(e.GetType()) {
			return false
		}
		if !e.GetSymbol().Mutable {
			if reference {
				v.errorf(e, "cannot take mutable reference of immutable variable")
			} else {
				v.errorf(e, "cannot assign to immutable symbol")
			}
			return false
		}
	case *parser.UnaryOpNode:
		if e.Op != parser.UnaryOpDereference {
			v.errorf(expr, "invalid assignment target")
			return false
		}
		return v.validateMutableAccessPath(e, true, reference)

	case *parser.FieldAccessNode:
		if e.TaggedUnionType != nil {
			if reference {
				v.errorf(e, "cannot take mutable reference of this expression")
			} else {
				v.errorf(e, "cannot assign to this expression")
			}
			return false
		}
		return v.validateMutableAccessPath(e, true, reference)

	case *parser.IndexExprNode:
		return v.validateMutableAccessPath(e, true, reference)

	default:
		if reference {
			v.errorf(expr, "cannot take reference of this expression")
		} else {
			v.errorf(expr, "cannot assign to this expression")
		}
		return false
	}

	return true
}

// validateMutableAccessPath validates every storage/view boundary used to
// reach a mutation. Pointer bindings are capabilities, so the binding itself
// need not be mutable, but every pointer that is dereferenced must be *mut.
// Mutable slices similarly carry element-write capability in their type.
func (v *Validator) validateMutableAccessPath(expr parser.ExpressionNode, requireMutableRoot bool, reference bool) bool {
	switch e := expr.(type) {
	case *parser.IdentifierNode:
		v.validateExpr(e)
		if types.HasError(e.GetType()) {
			return false
		}
		if !requireMutableRoot || e.Symbol == nil {
			return true
		}
		switch t := types.Underlying(e.GetType()).(type) {
		case types.PointerType:
			return true
		case types.SliceType:
			if t.Mutable {
				return true
			}
		}
		if !e.Symbol.Mutable {
			if reference {
				v.errorf(e, "cannot take mutable reference through immutable variable")
			} else {
				v.errorf(e, "cannot assign through immutable symbol")
			}
			return false
		}
		return true

	case *parser.FieldAccessNode:
		if e.TaggedUnionType != nil {
			if reference {
				v.errorf(e, "cannot take mutable reference of this expression")
			} else {
				v.errorf(e, "cannot assign to this expression")
			}
			return false
		}
		if e.ResolvedIdentifier != nil {
			return v.validateMutableAccessPath(e.ResolvedIdentifier, requireMutableRoot, reference)
		}
		if !v.validateMutableAccessPath(e.Subject, requireMutableRoot, reference) {
			return false
		}
		if types.HasError(e.Subject.GetType()) {
			return false
		}
		if pointer, ok := types.Underlying(e.Subject.GetType()).(types.PointerType); ok {
			return v.validateMutablePointerBoundary(e, pointer, reference)
		}
		return true

	case *parser.IndexExprNode:
		if !v.validateMutableAccessPath(e.Subject, requireMutableRoot, reference) {
			return false
		}
		switch subject := types.Underlying(e.Subject.GetType()).(type) {
		case types.SliceType:
			if !subject.Mutable {
				if reference {
					v.errorf(e, "cannot take mutable reference through immutable slice view")
				} else {
					v.errorf(e, "cannot assign through immutable slice view")
				}
				return false
			}
		case types.ArrayType:
			return true
		case types.PointerType:
			return v.validateMutablePointerBoundary(e, subject, reference)
		case types.ErrorType:
			return false
		default:
			if reference {
				v.errorf(e, "cannot take mutable reference through non-indexable value")
			} else {
				v.errorf(e, "cannot assign to index of non-slice type")
			}
			return false
		}
		return true

	case *parser.SliceExprNode:
		return v.validateMutableAccessPath(e.Subject, requireMutableRoot, reference)

	case *parser.UnaryOpNode:
		if e.Op != parser.UnaryOpDereference {
			break
		}
		pointer, ok := types.Underlying(e.Operand.GetType()).(types.PointerType)
		if !ok {
			return false
		}
		if !v.validateMutableAccessPath(e.Operand, requireMutableRoot, reference) {
			return false
		}
		return v.validateMutablePointerBoundary(e, pointer, reference)

	case *parser.FunctionCallNode:
		v.validateExpr(e)
		if types.HasError(e.GetType()) {
			return false
		}
		switch types.Underlying(e.GetType()).(type) {
		case types.PointerType, types.SliceType:
			return true
		}
		break

	default:
		// Casts and pointer arithmetic may also produce a capability. Their
		// validation prevents immutable-to-mutable upgrades.
		v.validateExpr(expr)
		if types.HasError(expr.GetType()) {
			return false
		}
		switch types.Underlying(expr.GetType()).(type) {
		case types.PointerType, types.SliceType:
			return true
		}
	}

	if reference {
		v.errorf(expr, "cannot take mutable reference through this expression")
	} else {
		v.errorf(expr, "cannot assign through this expression")
	}
	return false
}

func (v *Validator) validateMutablePointerBoundary(node parser.Node, pointer types.PointerType, reference bool) bool {
	if pointer.Base.Equals(types.PrimitiveVoid) {
		if reference {
			v.errorf(node, "cannot take mutable reference through void pointer")
		} else {
			v.errorf(node, "cannot assign to dereferenced void pointer")
		}
		return false
	}
	if !pointer.Mutable {
		if reference {
			v.errorf(node, "cannot take mutable reference through immutable pointer")
		} else {
			v.errorf(node, "cannot assign through immutable pointer")
		}
		return false
	}
	return true
}

func (v *Validator) validateIf(n *parser.IfNode) {
	v.validateExpr(n.IfBranch.Condition)
	if !types.HasError(n.IfBranch.Condition.GetType()) && !n.IfBranch.Condition.GetType().Equals(types.PrimitiveBool) {
		v.errorf(n, "if condition must be bool")
	}

	v.validateNode(n.IfBranch.Node)

	for _, elif := range n.ElseIfBranches {
		v.validateExpr(elif.Condition)
		if !types.HasError(elif.Condition.GetType()) && !elif.Condition.GetType().Equals(types.PrimitiveBool) {
			v.errorf(n, "elseif condition must be bool")
		}
		v.validateNode(elif.Node)
	}

	if n.ElseBranch != nil {
		v.validateNode(n.ElseBranch)
	}
}

func (v *Validator) validateExpressionBlock(n *parser.BlockNode, expected types.Type) {
	result, hasResult := parser.BlockResult(n)
	last := len(n.Body)
	if hasResult {
		last--
	}
	for _, child := range n.Body[:last] {
		v.validateStatement(child)
	}

	if !hasResult {
		if parser.NodeFallsThrough(n) {
			v.errorf(n, "block used as expression must end with an expression")
			n.SetType(types.ErrorType{})
		}
		return
	}

	if !parser.NodeFallsThrough(n) {
		v.validateStatement(result)
		return
	}

	if expected != nil {
		result = v.validateExprWithExpected(result, expected)
		n.Body[len(n.Body)-1] = result
		if types.HasError(result.GetType()) {
			n.SetType(types.ErrorType{})
		} else {
			n.SetType(expected)
		}
	} else {
		if !v.validateValueExpr(result) {
			n.SetType(types.ErrorType{})
			return
		}
		n.SetType(result.GetType())
	}
}

func (v *Validator) validateIfExpression(n *parser.IfNode, expected types.Type) {
	v.validateExpr(n.IfBranch.Condition)
	poisoned := types.HasError(n.IfBranch.Condition.GetType())
	if !poisoned && !n.IfBranch.Condition.GetType().Equals(types.PrimitiveBool) {
		v.errorf(n, "if expression condition must be bool")
	}

	validateBranch := func(block *parser.BlockNode) {
		if expected != nil && parser.NodeFallsThrough(block) {
			v.validateExprWithExpected(block, expected)
		} else {
			v.validateExpr(block)
		}
	}
	validateBranch(n.IfBranch.Node)
	for _, branch := range n.ElseIfBranches {
		v.validateExpr(branch.Condition)
		poisoned = poisoned || types.HasError(branch.Condition.GetType())
		if !types.HasError(branch.Condition.GetType()) && !branch.Condition.GetType().Equals(types.PrimitiveBool) {
			v.errorf(n, "elseif condition must be bool")
		}
		validateBranch(branch.Node)
	}
	if n.ElseBranch == nil {
		v.errorf(n, "if expression requires an else branch")
	} else {
		validateBranch(n.ElseBranch)
	}
	if poisoned {
		n.SetType(types.ErrorType{})
		return
	}
	branches := []*parser.BlockNode{n.IfBranch.Node}
	for _, branch := range n.ElseIfBranches {
		branches = append(branches, branch.Node)
	}
	branches = append(branches, n.ElseBranch)
	for _, branch := range branches {
		if branch != nil && types.HasError(branch.GetType()) {
			n.SetType(types.ErrorType{})
			return
		}
	}
	if expected != nil {
		n.SetType(expected)
	} else if types.HasError(n.GetType()) {
		candidates := make([]returnTypeCandidate, 0, len(branches))
		for _, branch := range branches {
			if branch != nil && parser.NodeFallsThrough(branch) {
				candidates = append(candidates, returnTypeCandidate{node: branch, ty: branch.GetType()})
			}
		}
		v.reportInconsistentExpressionResults(candidates, "inconsistent if expression branch types")
	}
}

func (v *Validator) validateMatch(n *parser.MatchNode, expected types.Type) {
	subjectTypes := make([]types.Type, len(n.Subjects))
	poisoned := false
	for i, subject := range n.Subjects {
		v.validateValueExpr(subject)
		subjectTypes[i] = subject.GetType()
		poisoned = poisoned || types.HasError(subjectTypes[i])
	}
	domains := v.matchCoverageDomains(subjectTypes, n.Arms)
	var covered []matchCoverageRow
	for i := range n.Arms {
		arm := &n.Arms[i]
		for patternIndex, pattern := range arm.Patterns {
			subjectIndex := patternIndex
			if len(arm.Patterns) == 1 && pattern.Kind == parser.MatchPatternWildcard {
				subjectIndex = 0
			}
			v.validateMatchPattern(pattern, subjectTypes[subjectIndex])
		}
		row := v.matchCoverageRow(arm.Patterns, len(subjectTypes))
		if !poisoned && matchCoverageContains(covered, row, domains) {
			v.errorf(arm.Patterns[0], "match arm is unreachable because an earlier arm already covers its pattern")
		}
		if arm.Guard != nil {
			v.validateExpr(arm.Guard)
			poisoned = poisoned || types.HasError(arm.Guard.GetType())
			if !types.HasError(arm.Guard.GetType()) && !arm.Guard.GetType().Equals(types.PrimitiveBool) {
				v.errorf(arm.Guard, "match arm guard must be bool")
			}
		} else {
			covered = append(covered, row)
		}
		if n.Expression {
			if !parser.NodeFallsThrough(arm.Body) {
				v.validateExpr(arm.Body)
			} else if expected != nil {
				arm.Body = v.validateExprWithExpected(arm.Body, expected)
			} else {
				v.validateValueExpr(arm.Body)
			}
		} else {
			v.validateStatement(arm.Body)
		}
		poisoned = poisoned || types.HasError(arm.Body.GetType())
		for _, pattern := range arm.Patterns {
			for _, binding := range matchPatternBindings(pattern) {
				v.warnIfUnused(binding.Symbol, binding.Loc, shared.WarningUnusedVariable, "match binding")
			}
		}
	}
	if n.Binding != nil {
		v.warnIfUnused(n.Binding, n.BindingLoc, shared.WarningUnusedVariable, "match subject binding")
	}
	if !poisoned && !matchCoverageExhaustive(covered, domains) {
		v.errorf(n, "match is not exhaustive; add the missing cases or a '_' arm")
	}
	if !n.Expression {
		n.SetType(types.PrimitiveVoid)
	} else if poisoned {
		n.SetType(types.ErrorType{})
	} else if expected != nil {
		n.SetType(expected)
	} else if types.HasError(n.GetType()) {
		var candidates []returnTypeCandidate
		for i := range n.Arms {
			body := n.Arms[i].Body
			if parser.NodeFallsThrough(body) {
				candidates = append(candidates, returnTypeCandidate{node: body, ty: body.GetType()})
			}
		}
		v.reportInconsistentExpressionResults(candidates, "inconsistent match arm types")
	}
}

func (v *Validator) reportInconsistentExpressionResults(candidates []returnTypeCandidate, diagnostic string) {
	if len(candidates) < 2 {
		return
	}
	current := candidates[0].ty
	for _, candidate := range candidates[1:] {
		if types.HasError(current) || types.HasError(candidate.ty) {
			return
		}
		if types.IsNumeric(current) && types.IsNumeric(candidate.ty) {
			current = types.PromoteNumeric(current, candidate.ty)
			if types.HasError(current) {
				v.errorf(candidate.node, "%s", diagnostic)
				return
			}
			continue
		}
		if !current.Equals(candidate.ty) {
			v.errorf(candidate.node, "%s: expected %v, got %v", diagnostic, current, candidate.ty)
			return
		}
	}
}

func (v *Validator) validateMatchPattern(pattern *parser.MatchPatternNode, subjectType types.Type) {
	if pattern == nil {
		return
	}
	if types.HasError(subjectType) {
		return
	}
	switch pattern.Kind {
	case parser.MatchPatternWildcard, parser.MatchPatternVariant:
		return
	case parser.MatchPatternAlternative:
		for _, alternative := range pattern.Alternatives {
			if len(matchPatternBindings(alternative)) != 0 {
				v.errorf(alternative, "alternative match patterns cannot bind payload values")
			}
			v.validateMatchPattern(alternative, subjectType)
		}
	case parser.MatchPatternLiteral:
		switch pattern.Literal.(type) {
		case *parser.FloatLiteralNode:
			v.errorf(pattern, "floating-point match patterns are not supported; use an 'if' guard")
			return
		case *parser.StringLiteralNode, *parser.CStringLiteralNode:
			v.errorf(pattern, "string match patterns are not supported; use an 'if' guard")
			return
		}
		pattern.Literal = v.validateExprWithExpected(pattern.Literal, subjectType)
	case parser.MatchPatternRange:
		if !types.IsInteger(subjectType) {
			v.errorf(pattern, "range pattern requires an integer subject")
			return
		}
		pattern.Start = v.validateExprWithExpected(pattern.Start, subjectType)
		pattern.End = v.validateExprWithExpected(pattern.End, subjectType)
	}
}

type matchCoveragePattern struct {
	wildcard bool
	keys     map[string]bool
}

type matchCoverageRow []matchCoveragePattern

func (v *Validator) matchCoverageRow(patterns []*parser.MatchPatternNode, subjectCount int) matchCoverageRow {
	row := make(matchCoverageRow, subjectCount)
	if len(patterns) == 1 && patterns[0].Kind == parser.MatchPatternWildcard {
		for i := range row {
			row[i].wildcard = true
		}
		return row
	}
	for i, pattern := range patterns {
		row[i] = v.matchCoveragePattern(pattern)
	}
	return row
}

func (v *Validator) matchCoveragePattern(pattern *parser.MatchPatternNode) matchCoveragePattern {
	result := matchCoveragePattern{keys: map[string]bool{}}
	if pattern == nil {
		return result
	}
	switch pattern.Kind {
	case parser.MatchPatternWildcard:
		result.wildcard = true
	case parser.MatchPatternVariant, parser.MatchPatternLiteral:
		if key := v.matchPatternCoverageKey(pattern); key != "" {
			result.keys[key] = true
		}
	case parser.MatchPatternRange:
		result.keys[fmt.Sprintf("range:%s:%d", pattern.Loc.FilePath, pattern.Loc.Offset)] = true
	case parser.MatchPatternAlternative:
		for _, alternative := range pattern.Alternatives {
			part := v.matchCoveragePattern(alternative)
			result.wildcard = result.wildcard || part.wildcard
			for key := range part.keys {
				result.keys[key] = true
			}
		}
	}
	return result
}

func (v *Validator) matchCoverageDomains(subjectTypes []types.Type, arms []parser.MatchArmNode) [][]string {
	domains := make([][]string, len(subjectTypes))
	seen := make([]map[string]bool, len(subjectTypes))
	for i, subjectType := range subjectTypes {
		seen[i] = map[string]bool{}
		if info, tagged := types.TaggedUnion(subjectType); tagged {
			for _, variant := range info.Variants {
				key := "variant-value:" + variant.TagValue
				seen[i][key] = true
				domains[i] = append(domains[i], key)
			}
		} else if enumType, ok := types.Underlying(subjectType).(types.EnumType); ok {
			for _, value := range enumType.Values {
				key := "variant-value:" + value
				seen[i][key] = true
				domains[i] = append(domains[i], key)
			}
		} else if subjectType.Equals(types.PrimitiveBool) {
			domains[i] = append(domains[i], "bool:true", "bool:false")
			seen[i]["bool:true"], seen[i]["bool:false"] = true, true
		} else {
			domains[i] = append(domains[i], "other")
			seen[i]["other"] = true
		}
	}
	for _, arm := range arms {
		if len(arm.Patterns) == 1 && arm.Patterns[0].Kind == parser.MatchPatternWildcard {
			continue
		}
		for i, pattern := range arm.Patterns {
			coverage := v.matchCoveragePattern(pattern)
			for key := range coverage.keys {
				if !seen[i][key] {
					seen[i][key] = true
					domains[i] = append(domains[i], key)
				}
			}
		}
	}
	return domains
}

func matchCoverageContains(covered []matchCoverageRow, target matchCoverageRow, domains [][]string) bool {
	if matchCoverageHasWildcardRow(covered) {
		return true
	}
	budget := 65536
	found, fullyCovered := false, true
	var visit func(int, []string)
	visit = func(dimension int, point []string) {
		if !fullyCovered || budget == 0 {
			fullyCovered = false
			return
		}
		if dimension == len(domains) {
			budget--
			found = true
			if !matchCoveragePointCovered(covered, point) {
				fullyCovered = false
			}
			return
		}
		for _, value := range domains[dimension] {
			if target[dimension].wildcard || target[dimension].keys[value] {
				visit(dimension+1, append(point, value))
			}
		}
	}
	visit(0, nil)
	return found && fullyCovered
}

func matchCoverageExhaustive(covered []matchCoverageRow, domains [][]string) bool {
	if matchCoverageHasWildcardRow(covered) {
		return true
	}
	budget := 65536
	exhaustive := true
	var visit func(int, []string)
	visit = func(dimension int, point []string) {
		if !exhaustive || budget == 0 {
			exhaustive = false
			return
		}
		if dimension == len(domains) {
			budget--
			if !matchCoveragePointCovered(covered, point) {
				exhaustive = false
			}
			return
		}
		for _, value := range domains[dimension] {
			visit(dimension+1, append(point, value))
		}
	}
	visit(0, nil)
	return exhaustive
}

func matchCoverageHasWildcardRow(rows []matchCoverageRow) bool {
	for _, row := range rows {
		allWildcard := true
		for _, pattern := range row {
			allWildcard = allWildcard && pattern.wildcard
		}
		if allWildcard {
			return true
		}
	}
	return false
}

func matchCoveragePointCovered(rows []matchCoverageRow, point []string) bool {
	for _, row := range rows {
		matches := true
		for i, value := range point {
			matches = matches && (row[i].wildcard || row[i].keys[value])
		}
		if matches {
			return true
		}
	}
	return false
}

func (v *Validator) matchPatternCoverageKey(pattern *parser.MatchPatternNode) string {
	if pattern == nil {
		return ""
	}
	if pattern.Kind == parser.MatchPatternVariant {
		if pattern.TagValue != "" {
			return "variant-value:" + pattern.TagValue
		}
		return "variant:" + pattern.Variant
	}
	if pattern.Kind != parser.MatchPatternLiteral {
		return ""
	}
	switch literal := pattern.Literal.(type) {
	case *parser.BoolLiteralNode:
		return "bool:" + literal.Value
	case *parser.IntegerLiteralNode:
		return "integer:" + literal.Value
	case *parser.CharLiteralNode:
		return fmt.Sprintf("integer:%d", literal.Value)
	}
	return ""
}

func (v *Validator) validateFor(n *parser.ForNode) {
	switch len(n.ExprsOrStmts) {
	case 0:
	case 1:
		condition, ok := n.ExprsOrStmts[0].(parser.ExpressionNode)
		if !ok {
			v.errorf(n.ExprsOrStmts[0], "for loop condition must be an expression")
		} else {
			v.validateExpr(condition)
			if !types.HasError(condition.GetType()) && !condition.GetType().Equals(types.PrimitiveBool) {
				v.errorf(condition, "for loop condition must be bool")
			}
		}
	case 3:
		v.validateStatement(n.ExprsOrStmts[0])
		condition, ok := n.ExprsOrStmts[1].(parser.ExpressionNode)
		if !ok {
			v.errorf(n.ExprsOrStmts[1], "for loop condition must be an expression")
		} else {
			v.validateExpr(condition)
			if !types.HasError(condition.GetType()) && !condition.GetType().Equals(types.PrimitiveBool) {
				v.errorf(condition, "for loop condition must be bool")
			}
		}
		v.validateStatement(n.ExprsOrStmts[2])
	default:
		v.errorf(n, "for loop must have a condition or initializer, condition, and post expression")
		return
	}

	v.validateLoopBody(n.Body)
}

func (v *Validator) validateRangeFor(n *parser.RangeForNode) {
	v.validateExpr(n.Start)
	v.validateExpr(n.End)
	if anyErrorExpression(n.Start, n.End) {
		if n.Symbol != nil {
			n.Symbol.Type = types.ErrorType{}
		}
		v.validateLoopBody(n.Body)
		return
	}

	boundType := types.PromoteNumeric(n.Start.GetType(), n.End.GetType())
	if !types.IsInteger(boundType) {
		v.errorf(n, "range bounds must be integer types")
		return
	}
	if n.Symbol != nil {
		n.Symbol.Type = types.PrimitiveUsz
	}

	v.validateLoopBody(n.Body)
	v.warnIfUnused(n.Symbol, n.NameLoc, shared.WarningUnusedVariable, "variable")

	iteratorType := types.PrimitiveUsz
	n.Start = v.validateExprWithExpected(n.Start, iteratorType)
	n.End = v.validateExprWithExpected(n.End, iteratorType)
	if n.Symbol != nil {
		n.Symbol.Type = iteratorType
	}
}

func (v *Validator) validateForEach(n *parser.ForEachNode) {
	v.validateExpr(n.Iterable)
	if types.HasError(n.Iterable.GetType()) {
		if n.Symbol != nil {
			n.Symbol.Type = types.ErrorType{}
		}
		if n.IndexSymbol != nil {
			n.IndexSymbol.Type = types.ErrorType{}
		}
		v.validateLoopBody(n.Body)
		return
	}
	var element types.Type
	iterableType := types.Underlying(n.Iterable.GetType())
	switch iterable := iterableType.(type) {
	case types.SliceType:
		element = iterable.Base
	case types.ArrayType:
		element = iterable.Base
	default:
		v.errorf(n, "for loop iterable must be an array or slice")
		return
	}
	if n.ElementKind != parser.ForEachElementValue {
		slice, ok := iterableType.(types.SliceType)
		if !ok {
			v.errorf(n, "element pointer iteration requires a slice or string")
			return
		}
		if n.ElementKind == parser.ForEachElementMutablePointer && !slice.Mutable {
			v.errorf(n, "mutable element pointer iteration requires a mutable slice")
			return
		}
	}
	if types.HasUntyped(element) {
		v.errorf(n, "cannot infer for loop element type from untyped array or slice")
		return
	}
	if len(n.Destructure) != 0 {
		componentTypes, destructurable := forEachDestructureTypes(element)
		if !destructurable {
			v.errorf(n, "for loop destructuring requires an array or struct element")
			return
		}
		if len(n.Destructure) != len(componentTypes) {
			v.errorf(n, "destructuring pattern has %d bindings, but loop element has %d values",
				len(n.Destructure), len(componentTypes))
			return
		}
		for i := range n.Destructure {
			if n.Destructure[i].Symbol != nil {
				n.Destructure[i].Symbol.Type = componentTypes[i]
			}
		}
	} else if n.Symbol != nil {
		n.Symbol.Type = forEachElementType(n, element)
	}
	if n.IndexSymbol != nil {
		n.IndexSymbol.Type = types.PrimitiveUsz
	}
	v.validateLoopBody(n.Body)
	if len(n.Destructure) != 0 {
		for i := range n.Destructure {
			binding := &n.Destructure[i]
			v.warnIfUnused(binding.Symbol, binding.Loc, shared.WarningUnusedVariable, "variable")
		}
	} else {
		v.warnIfUnused(n.Symbol, n.NameLoc, shared.WarningUnusedVariable, "variable")
	}
	if n.IndexName != "" {
		v.warnIfUnused(n.IndexSymbol, n.IndexNameLoc, shared.WarningUnusedVariable, "variable")
	}
}

func (v *Validator) validateLoopBody(body *parser.BlockNode) {
	v.loopDepth++
	v.validateNode(body)
	v.loopDepth--
}

func (v *Validator) validateReturn(n *parser.ControlKeywordNode) {
	if v.currentFunction == nil {
		v.errorf(n, "return outside of function")
		return
	}
	expected := v.currentFunction.Signature.ReturnType
	if multi, ok := expected.(types.MultipleReturnType); ok {
		if len(n.ReturnValues) == 1 {
			switch n.ReturnValue.(type) {
			case *parser.FunctionCallNode, *parser.InlineAsmNode:
				n.ReturnValue = v.validateExprWithExpected(n.ReturnValue, expected)
				return
			}
		}
		if len(n.ReturnValues) != len(multi.Types) {
			v.errorf(n, "return has %d values but function returns %d", len(n.ReturnValues), len(multi.Types))
			return
		}
		for i := range n.ReturnValues {
			n.ReturnValues[i] = v.validateExprWithExpected(n.ReturnValues[i], multi.Types[i])
		}
		return
	}
	if len(n.ReturnValues) > 1 {
		v.errorf(n, "too many return values")
		return
	}
	n.ReturnValue = v.validateExprWithExpected(n.ReturnValue, expected)
}

func (v *Validator) validateExpr(node parser.ExpressionNode) {
	if lambda, ok := node.(*parser.LambdaNode); ok {
		v.prepareLambda(lambda, nil)
		if types.HasError(lambda.GetType()) {
			return
		}
		if lambda.Function != nil && !lambda.Validated {
			lambda.Validated = true
			v.validateNode(lambda.Function)
		}
		return
	}
	if types.HasError(node.GetType()) {
		// Expression containers can be poisoned solely because their otherwise
		// valid result branches disagree. Visit them so validation can either use
		// surrounding context or issue a result-specific diagnostic.
		switch node.(type) {
		case *parser.BlockNode, *parser.IfNode, *parser.MatchNode:
		default:
			return
		}
	}

	switch n := node.(type) {
	case *parser.InlineAsmNode:
		v.validateInlineAsm(n)

	case *parser.EnumLiteralNode:
		v.errorf(n, "cannot infer enum type for .%s", n.Variant)
		n.SetType(types.ErrorType{})

	case *parser.FunctionCallNode:
		if n.CompileTimeApplication != nil {
			return
		}
		if !v.completeGenericCall(n, nil) {
			return
		}
		if n.TaggedUnionType != nil {
			info, _ := types.TaggedUnion(n.TaggedUnionType)
			if n.TaggedUnionVariant < 0 || n.TaggedUnionVariant >= len(info.Variants) {
				v.errorf(n, "invalid tagged union constructor")
				return
			}
			variant := info.Variants[n.TaggedUnionVariant]
			if len(n.Args) != len(variant.Fields) {
				v.errorf(n, "tagged union variant %q expects %d arguments, but %d provided", variant.Name, len(variant.Fields), len(n.Args))
				return
			}
			for i := range n.Args {
				n.Args[i] = v.validateExprWithExpected(n.Args[i], variant.Fields[i].R)
			}
			if anyErrorExpression(n.Args...) {
				n.SetType(types.ErrorType{})
				return
			}
			n.SetType(n.TaggedUnionType)
			return
		}
		var params []types.Type
		variadic := false
		typedVariadic := false
		var variadicElement types.Type
		if n.Symbol != nil && n.Symbol.Kind == symbols.SymbolKindFunction {
			params = n.Symbol.Signature.Parameters
			variadic = n.Symbol.Signature.Variadic
			typedVariadic = n.Symbol.Signature.TypedVariadic
			variadicElement = n.Symbol.Signature.VariadicElement
		} else {
			v.validateExpr(n.Callee)
			if types.HasError(n.Callee.GetType()) {
				n.SetType(types.ErrorType{})
				return
			}
			ptr, ok := types.Underlying(n.Callee.GetType()).(types.PointerType)
			if !ok {
				v.errorf(n, "expression of type %s is not callable", n.Callee.GetType())
				return
			}
			fn, ok := types.Underlying(ptr.Base).(types.FunctionType)
			if !ok {
				v.errorf(n, "expression of type %s is not callable", n.Callee.GetType())
				return
			}
			params = fn.Parameters
			typedVariadic = fn.TypedVariadic
			variadicElement = fn.VariadicElement
		}

		got, expected := len(n.Args), len(params)
		if typedVariadic {
			expected--
			variadic = true
		}
		required := expected
		if n.Symbol != nil && n.Symbol.Kind == symbols.SymbolKindFunction {
			required = n.Symbol.Signature.RequiredParameters
		}
		if n.Method {
			got--
			expected--
			required--
		}
		if got < required || (!variadic && got > expected) {
			callable := "callable expression"
			if n.Symbol != nil {
				kind := "function"
				if n.Method {
					kind = "method"
				}
				callable = fmt.Sprintf("%s %q", kind, n.Symbol.Name)
			} else if n.Callee != nil {
				callable = fmt.Sprintf("callable of type %v", n.Callee.GetType())
			}
			if variadic {
				v.errorf(n, "%s expects at least %d %s, but %d %s provided",
					callable, required, argumentWord(required), got, wasWere(got))
			} else if required != expected {
				v.errorf(n, "%s expects between %d and %d arguments, but %d %s provided",
					callable, required, expected, got, wasWere(got))
			} else {
				v.errorf(n, "%s expects %d %s, but %d %s provided",
					callable, expected, argumentWord(expected), got, wasWere(got))
			}
			return
		}

		for i, arg := range n.Args {
			if typedVariadic && i >= len(params)-1 {
				if n.VariadicExpansion {
					if i != len(params)-1 || len(n.Args) != len(params) {
						v.errorf(n, "slice expansion must supply the complete typed variadic tail")
						continue
					}
					n.Args[i] = v.validateExprWithExpected(arg, params[len(params)-1])
				} else {
					n.Args[i] = v.validateExprWithExpected(arg, variadicElement)
				}
				continue
			}
			if i >= len(params) {
				v.validateValueExpr(arg)
				if types.HasUntyped(arg.GetType()) {
					v.errorf(arg, "cannot infer type for variadic argument from untyped numeric value; add a cast")
				}
				continue
			}

			paramType := params[i]
			n.Args[i] = v.validateExprWithExpected(arg, paramType)
		}
		if anyErrorExpression(n.Args...) {
			n.SetType(types.ErrorType{})
			return
		}
		if n.Symbol != nil && n.Symbol.Signature != nil &&
			n.Symbol.Attributes.Get(attributes.AttributeTypeForeign) == nil {
			fixedCount := len(n.Symbol.Signature.Parameters)
			if n.Symbol.Signature.TypedVariadic {
				fixedCount--
			}
			crossModule := v.currentFunction != nil &&
				v.currentFunction.DefinitionModule != n.Symbol.DefinitionModule
			for i := 0; i < len(n.Args) && i < fixedCount; i++ {
				if constant, ok := specializationConstant(n.Args[i]); crossModule && ok {
					if n.Symbol.Signature.ConstantArguments == nil {
						n.Symbol.Signature.ConstantArguments = make(map[int]map[string]symbols.SpecializationConstant)
					}
					if n.Symbol.Signature.ConstantArguments[i] == nil {
						n.Symbol.Signature.ConstantArguments[i] = make(map[string]symbols.SpecializationConstant)
					}
					n.Symbol.Signature.ConstantArguments[i][constant.Key()] = constant
					continue
				}
				identifier, ok := n.Args[i].(*parser.IdentifierNode)
				if !ok || identifier.Symbol == nil || identifier.Symbol.Mutable || v.currentFunction == nil {
					continue
				}
				callerParameter, forwarded := v.currentFunctionParameters[identifier.Symbol]
				if !forwarded {
					continue
				}
				v.currentFunction.Signature.ConstantForwards = append(
					v.currentFunction.Signature.ConstantForwards,
					symbols.ConstantForward{
						CallerParameter: callerParameter,
						Callee:          n.Symbol.Signature,
						CalleeParameter: i,
					},
				)
			}
		}
		if typedVariadic {
			n.TypedVariadic = true
			n.TypedVariadicStart = len(params) - 1
			n.TypedVariadicSlice = params[len(params)-1]
			if n.Symbol != nil && n.Symbol.Attributes.Get(attributes.AttributeTypeForeign) == nil && !n.VariadicExpansion {
				if n.Symbol.Signature.TypedVariadicArities == nil {
					n.Symbol.Signature.TypedVariadicArities = make(map[int]bool)
				}
				n.Symbol.Signature.TypedVariadicArities[len(n.Args)-n.TypedVariadicStart] = true
			} else if n.Symbol != nil && n.Symbol.Attributes.Get(attributes.AttributeTypeForeign) == nil &&
				n.VariadicExpansion && v.currentTypedVariadicParameter != nil {
				if argument, ok := n.Args[n.TypedVariadicStart].(*parser.IdentifierNode); ok &&
					argument.Symbol == v.currentTypedVariadicParameter {
					v.currentFunction.Signature.TypedVariadicForwards = append(
						v.currentFunction.Signature.TypedVariadicForwards,
						n.Symbol.Signature,
					)
				}
			}
		} else if n.VariadicExpansion {
			v.errorf(n, "slice expansion requires a typed variadic function")
		}

	case *parser.CastNode:
		targetType := n.Type
		if n.Checked {
			targetType = n.CheckedType
		}
		if literal, ok := n.Operand.(*parser.SliceLiteralNode); ok {
			v.validateSliceLiteralWithExpected(literal, targetType)
			break
		}
		if identifier := comptimeIdentifier(n.Operand); identifier != nil && identifier.Symbol != nil &&
			identifier.Symbol.InlineComptime && types.IsUntyped(n.Operand.GetType()) {
			n.Operand = v.validateExprWithExpected(n.Operand, targetType)
		} else {
			v.validateExpr(n.Operand)
		}
		if types.HasError(n.Operand.GetType()) || types.HasError(targetType) {
			n.SetType(types.ErrorType{})
			return
		}
		if n.StaticTraitView != nil {
			v.validateStaticTraitAssertion(n)
			break
		}
		if conversion, ok := v.traitConversion(n.Operand, targetType); ok {
			n.TraitConversion = true
			n.ConcreteType = conversion.ConcreteType
			n.TraitMethods = conversion.TraitMethods
			break
		}
		if target, ok := v.analyser.traitPointer(targetType); ok {
			got := n.Operand.GetType()
			parameter, symbolic := genericTypeParameterBase(got)
			constraint, constrained := types.TraitType{}, false
			if symbolic {
				constraint, constrained = types.Underlying(parameter.Constraint).(types.TraitType)
			}
			pointer, isPointer := types.Underlying(got).(types.PointerType)
			if constrained && isPointer && traitImplementsTrait(constraint, target.Trait) {
				if target.Mutable && !pointer.Mutable {
					v.errorf(n, "cannot cast immutable %v to mutable %v", got, targetType)
					break
				}
				n.ConcreteType = pointer.Base
				n.TraitConversion = true
				n.GenericAssertion = false
				break
			}
		}
		if types.IsUntyped(n.Operand.GetType()) {
			n.Operand = v.createCast(n.Operand, targetType)
		}

		if sourceTrait, ok := v.analyser.traitPointer(n.Operand.GetType()); ok {
			if targetTrait, targetIsTrait := v.analyser.traitPointer(targetType); targetIsTrait {
				if !sourceTrait.Mutable && targetTrait.Mutable {
					v.errorf(n, "cannot cast immutable %v to mutable %v", n.Operand.GetType(), targetType)
					break
				}
				n.TraitRecast = true
				n.TraitCandidates = v.analyser.runtimeTraitCastCandidates(targetTrait.Trait, targetTrait.Mutable, n)
				break
			}
			targetPointer, pointerTarget := types.Underlying(targetType).(types.PointerType)
			if pointerTarget {
				if !sourceTrait.Mutable && targetPointer.Mutable {
					v.errorf(n, "cannot unwrap immutable %v as %v", n.Operand.GetType(), targetType)
				}
				n.TraitUnwrap = true
				n.ConcreteType = targetPointer.Base
				probe := types.PointerType{Base: targetPointer.Base, Mutable: sourceTrait.Mutable}
				if _, conforms := v.analyser.structuralConformance(probe, sourceTrait, n); !conforms {
					if n.Checked {
						n.TraitUnwrap = false
						n.StaticAssertion = true
						n.AssertionMatches = false
					} else {
						v.errorf(n, "type %v does not conform to %v", targetPointer.Base, sourceTrait.Trait)
					}
				}
				break
			}
			if !types.IsComplete(targetType) {
				v.errorf(n, "cannot unwrap incomplete type %v by value", targetType)
				break
			}
			n.TraitUnwrap = true
			n.ConcreteType = targetType
			probe := types.PointerType{Base: targetType, Mutable: sourceTrait.Mutable}
			if _, conforms := v.analyser.structuralConformance(probe, sourceTrait, n); !conforms {
				if n.Checked {
					n.TraitUnwrap = false
					n.StaticAssertion = true
					n.AssertionMatches = false
				} else {
					v.errorf(n, "type %v does not conform to %v", targetType, sourceTrait.Trait)
				}
			}
			break
		}
		if n.GenericAssertion {
			if _, sourceIsSlice := types.Underlying(n.Operand.GetType()).(types.SliceType); sourceIsSlice {
				if _, targetIsPointer := types.Underlying(targetType).(types.PointerType); targetIsPointer {
					n.GenericAssertion = false
					if n.Checked {
						n.StaticAssertion = true
						n.AssertionMatches = false
					} else {
						v.errorf(n, "cannot cast %v to %v; use a full slice expression such as value[:] to expose its data pointer", n.Operand.GetType(), targetType)
					}
					break
				}
			}
			n.AssertionMatches = n.Operand.GetType().Equals(targetType)
			break
		}
		if sourceArray, ok := types.Underlying(n.Operand.GetType()).(types.ArrayType); ok {
			if targetSlice, ok := types.Underlying(targetType).(types.SliceType); ok && sourceArray.Base.Equals(targetSlice.Base) {
				reference := &parser.UnaryOpNode{Operand: n.Operand, Loc: n.Operand.GetLoc()}
				if !v.validateReferenceTarget(reference, n.Operand, targetSlice.Mutable) {
					break
				}
				return
			}
		}
		if !types.CanExplicitCast(n.Operand.GetType(), targetType) {
			if n.Checked {
				n.StaticAssertion = true
				n.AssertionMatches = false
				break
			}
			v.errorf(n, "cannot cast %v to %v", n.Operand.GetType(), targetType)
		}

	case *parser.ReprNode:
		v.validateExpr(n.Operand)
		if types.HasError(n.Operand.GetType()) {
			n.SetType(types.ErrorType{})
			return
		}

	case *parser.UnaryOpNode:
		v.validateExpr(n.Operand)

		operandType := n.Operand.GetType()
		if types.HasError(operandType) {
			n.SetType(types.ErrorType{})
			return
		}

		switch n.Op {

		case parser.UnaryOpLogicalNot:
			if !operandType.Equals(types.PrimitiveBool) {
				v.errorf(n, "operator ! requires bool")
			}

		case parser.UnaryOpNegate:
			if !types.IsSigned(operandType) && !types.IsFloat(operandType) {
				v.errorf(n, "operator - requires a signed integer or float")
			}

		case parser.UnaryOpBitwiseNot:
			if !types.IsInteger(operandType) && !isFlagsType(operandType) {
				v.errorf(n, "operator ~ requires an integer")
			}

		case parser.UnaryOpReference:
			v.validateReferenceTarget(n, n.Operand, false)
		case parser.UnaryOpMutableReference:
			v.validateReferenceTarget(n, n.Operand, true)

		case parser.UnaryOpDereference:
			if ptrType, ok := types.Underlying(operandType).(types.PointerType); ok {
				if ptrType.Base.Equals(types.PrimitiveVoid) {
					v.errorf(n, "cannot dereference void pointer")
				} else if !types.IsComplete(ptrType.Base) {
					v.errorf(n, "cannot dereference pointer to incomplete type %v", ptrType.Base)
				}
			} else {
				v.errorf(n, "cannot dereference non-pointer type")
			}

		case parser.UnaryOpSliceLen:
			switch types.Underlying(operandType).(type) {
			case types.SliceType, types.ArrayType:
				// OK
			default:
				v.errorf(n, "length operator requires an array or slice operand")
			}

		default:
			v.errorf(n, "unknown unary operator")
		}

	case *parser.BinaryOpNode:
		_, leftEnumShorthand := n.Operand1.(*parser.EnumLiteralNode)
		_, rightEnumShorthand := n.Operand2.(*parser.EnumLiteralNode)
		_, leftNil := n.Operand1.(*parser.NilLiteralNode)
		_, rightNil := n.Operand2.(*parser.NilLiteralNode)
		switch {
		case leftEnumShorthand && !rightEnumShorthand:
			v.validateExpr(n.Operand2)
			n.Operand1 = v.validateExprWithExpected(n.Operand1, n.Operand2.GetType())
		case rightEnumShorthand && !leftEnumShorthand:
			v.validateExpr(n.Operand1)
			n.Operand2 = v.validateExprWithExpected(n.Operand2, n.Operand1.GetType())
		case leftNil && !rightNil && (n.Op == parser.BinaryOpEqual || n.Op == parser.BinaryOpNotEqual):
			v.validateExpr(n.Operand2)
			n.Operand1 = v.validateExprWithExpected(n.Operand1, n.Operand2.GetType())
		case rightNil && !leftNil && (n.Op == parser.BinaryOpEqual || n.Op == parser.BinaryOpNotEqual):
			v.validateExpr(n.Operand1)
			n.Operand2 = v.validateExprWithExpected(n.Operand2, n.Operand1.GetType())
		default:
			v.validateExpr(n.Operand1)
			v.validateExpr(n.Operand2)
		}

		t1 := n.Operand1.GetType()
		t2 := n.Operand2.GetType()
		if types.HasError(t1) {
			n.SetType(types.ErrorType{})
			return
		}
		if types.HasError(t2) {
			n.SetType(types.ErrorType{})
			return
		}

		switch n.Op {

		case parser.BinaryOpAdd,
			parser.BinaryOpSubtract,
			parser.BinaryOpMultiply,
			parser.BinaryOpDivide,
			parser.BinaryOpModulo:
			leftPtr, leftIsPtr := types.Underlying(t1).(types.PointerType)
			rightPtr, rightIsPtr := types.Underlying(t2).(types.PointerType)
			if n.Op == parser.BinaryOpAdd && leftIsPtr && types.IsInteger(t2) {
				v.validatePointerArithmeticBase(n, leftPtr.Base)
				n.Operand2 = v.validateExprWithExpected(n.Operand2, types.PrimitiveIsz)
				break
			}
			if n.Op == parser.BinaryOpAdd && types.IsInteger(t1) && rightIsPtr {
				v.validatePointerArithmeticBase(n, rightPtr.Base)
				n.Operand1 = v.validateExprWithExpected(n.Operand1, types.PrimitiveIsz)
				break
			}
			if n.Op == parser.BinaryOpSubtract && leftIsPtr && types.IsInteger(t2) {
				v.validatePointerArithmeticBase(n, leftPtr.Base)
				n.Operand2 = v.validateExprWithExpected(n.Operand2, types.PrimitiveIsz)
				break
			}
			if n.Op == parser.BinaryOpSubtract && leftIsPtr && rightIsPtr {
				if !leftPtr.Base.Equals(rightPtr.Base) {
					v.errorf(n, "cannot subtract pointers to different types %v and %v", leftPtr.Base, rightPtr.Base)
				} else {
					v.validatePointerArithmeticBase(n, leftPtr.Base)
				}
				break
			}

			if !types.IsNumeric(t1) || !types.IsNumeric(t2) {
				v.errorf(n, "arithmetic operators require numeric operands")
				break
			}

			common := types.PromoteNumeric(t1, t2)
			if types.HasError(common) {
				v.errorf(n, "incompatible types for arithmetic: %v and %v", t1, t2)
				break
			}
			if !types.IsUntyped(common) {
				n.Operand1 = v.validateExprWithExpected(n.Operand1, common)
				n.Operand2 = v.validateExprWithExpected(n.Operand2, common)
				n.SetType(common)
			}

		case parser.BinaryOpLogicalAnd,
			parser.BinaryOpLogicalOr:

			if !t1.Equals(types.PrimitiveBool) ||
				!t2.Equals(types.PrimitiveBool) {
				v.errorf(n, "logical operators require bool operands")
			}

		case parser.BinaryOpBitwiseAnd,
			parser.BinaryOpBitwiseXor,
			parser.BinaryOpBitwiseOr,
			parser.BinaryOpShiftLeft,
			parser.BinaryOpShiftRight:

			if isFlagsType(t1) {
				if n.Op == parser.BinaryOpShiftLeft || n.Op == parser.BinaryOpShiftRight {
					if !types.IsInteger(t2) {
						v.errorf(n, "flags shift count must be an integer")
					}
				} else if !t1.Equals(t2) {
					v.errorf(n, "flags bitwise operands must have the same type")
				}
				n.SetType(t1)
				break
			}
			if !types.IsInteger(t1) || !types.IsInteger(t2) {
				v.errorf(n, "bitwise operators require integer operands")
				break
			}
			common := types.PromoteNumeric(t1, t2)
			if types.HasError(common) {
				v.errorf(n, "incompatible integer types for bitwise operation: %v and %v", t1, t2)
				break
			}
			if !types.IsUntyped(common) {
				n.Operand1 = v.validateExprWithExpected(n.Operand1, common)
				n.Operand2 = v.validateExprWithExpected(n.Operand2, common)
				n.SetType(common)
			}

		case parser.BinaryOpEqual,
			parser.BinaryOpNotEqual:

			if types.IsNumeric(t1) && types.IsNumeric(t2) {
				common := types.PromoteNumeric(t1, t2)
				if types.HasError(common) {
					v.errorf(n, "incompatible types for comparison: %v and %v", t1, t2)
					break
				}
				if types.IsUntyped(common) {
					v.errorf(n, "cannot infer numeric type for comparison")
					break
				}
				n.Operand1 = v.validateExprWithExpected(n.Operand1, common)
				n.Operand2 = v.validateExprWithExpected(n.Operand2, common)
			} else if !t1.CanCoerceTo(t2) && !t2.CanCoerceTo(t1) {
				v.errorf(n, "incompatible types for comparison: %v and %v", t1, t2)
			}

		case parser.BinaryOpLess,
			parser.BinaryOpLessEqual,
			parser.BinaryOpGreater,
			parser.BinaryOpGreaterEqual:

			if !types.IsNumeric(t1) || !types.IsNumeric(t2) {
				v.errorf(n, "ordering operators require numeric operands")
				break
			}
			common := types.PromoteNumeric(t1, t2)
			if types.IsUntyped(common) {
				v.errorf(n, "cannot infer numeric type for comparison")
				break
			}
			n.Operand1 = v.validateExprWithExpected(n.Operand1, common)
			n.Operand2 = v.validateExprWithExpected(n.Operand2, common)

		default:
			v.errorf(n, "unknown binary operator")
		}

	case *parser.IndexExprNode:
		v.validateExpr(n.Subject)
		v.validateExpr(n.Index)
		if anyErrorExpression(n.Subject, n.Index) {
			n.SetType(types.ErrorType{})
			return
		}

		indexType := n.Index.GetType()
		if !types.IsInteger(indexType) {
			v.errorf(n, "index must be integer")
		}

		switch subjectType := types.Underlying(n.Subject.GetType()).(type) {
		case types.SliceType:
		case types.ArrayType:
			if idxLit, ok := n.Index.(*parser.IntegerLiteralNode); ok {
				idx, _ := strconv.Atoi(idxLit.Value)
				if idx < 0 || idx >= subjectType.Length {
					v.errorf(n, "index %d out of bounds for array of length %d", idx, subjectType.Length)
				}
			}

		case types.PointerType:
			if !types.IsComplete(subjectType.Base) {
				v.errorf(n, "cannot index pointer to incomplete type %v", subjectType.Base)
			}
		default:
			v.errorf(n, "cannot index into non-array-or-slice type")
		}

	case *parser.SliceExprNode:
		v.validateExpr(n.Subject)
		if n.Start != nil {
			v.validateExpr(n.Start)
			if !types.IsInteger(n.Start.GetType()) {
				v.errorf(n.Start, "slice start must be integer")
			} else {
				n.Start = v.validateExprWithExpected(n.Start, types.PrimitiveUsz)
			}
		}
		if n.End != nil {
			v.validateExpr(n.End)
			if !types.IsInteger(n.End.GetType()) {
				v.errorf(n.End, "slice end must be integer")
			} else {
				n.End = v.validateExprWithExpected(n.End, types.PrimitiveUsz)
			}
		}
		if anyErrorExpression(n.Subject, n.Start, n.End) {
			n.SetType(types.ErrorType{})
			return
		}

		subjectType := types.Underlying(n.Subject.GetType())
		_, isSlice := subjectType.(types.SliceType)
		arrayType, isArray := subjectType.(types.ArrayType)
		pointerType, isPointer := subjectType.(types.PointerType)
		if !isSlice && !isArray && !isPointer {
			v.errorf(n, "cannot slice non-array, non-slice, or non-pointer type")
			break
		}
		if isPointer {
			if n.End == nil {
				v.errorf(n, "pointer slice requires an end bound")
			}
			if !types.IsComplete(pointerType.Base) {
				v.errorf(n, "cannot slice pointer to incomplete type %v", pointerType.Base)
			}
		}
		if isArray {
			result, _ := types.Underlying(n.GetType()).(types.SliceType)
			reference := &parser.UnaryOpNode{Operand: n.Subject, Loc: n.Subject.GetLoc()}
			v.validateReferenceTarget(reference, n.Subject, result.Mutable)
		}

		start, startKnown := staticIntegerValue(n.Start)
		if n.Start == nil {
			start = new(big.Int)
			startKnown = true
		}
		end, endKnown := staticIntegerValue(n.End)
		if n.End == nil && isArray {
			end = big.NewInt(int64(arrayType.Length))
			endKnown = true
		}

		if startKnown && start.Sign() < 0 {
			v.errorf(n.Start, "slice start cannot be negative")
		}
		if endKnown && end.Sign() < 0 {
			v.errorf(n.End, "slice end cannot be negative")
		}
		if startKnown && endKnown && start.Cmp(end) > 0 {
			v.errorf(n, "slice start %s exceeds end %s", start, end)
		}
		if isArray {
			size := big.NewInt(int64(arrayType.Length))
			if startKnown && start.Cmp(size) > 0 {
				v.errorf(n, "slice start %s out of bounds for array of length %d", start, arrayType.Length)
			}
			if endKnown && end.Cmp(size) > 0 {
				v.errorf(n, "slice end %s out of bounds for array of length %d", end, arrayType.Length)
			}
		}

	case *parser.FieldAccessNode:
		if n.TaggedUnionType != nil {
			return
		}
		if n.ResolvedIdentifier != nil && n.ResolvedIdentifier.Symbol != nil &&
			n.ResolvedIdentifier.Symbol.Kind == symbols.SymbolKindVariable &&
			types.HasError(n.ResolvedIdentifier.Symbol.Type) {
			n.ResolvedIdentifier.SetType(types.ErrorType{})
			n.SetType(types.ErrorType{})
			return
		}
		if n.MethodSymbol != nil && n.MethodSymbol.Template {
			v.errorf(n, "parameterized method %q requires type arguments when used as a value", n.Field.Name)
			return
		}
		if n.IsEnumValue || n.IsFlagValue || n.IsFlagTest || n.MethodSymbol != nil || n.ResolvedIdentifier != nil || n.ModulePath != "" {
			return
		}
		v.validateExpr(n.Subject)
		if types.HasError(n.Subject.GetType()) {
			n.SetType(types.ErrorType{})
			return
		}
		subjectType := types.Underlying(n.Subject.GetType())
		if ptr, ok := subjectType.(types.PointerType); ok {
			subjectType = types.Underlying(ptr.Base)
		}
		if unionType, ok := subjectType.(types.UnionType); ok {
			for _, field := range unionType.Fields {
				if field.L == n.Field.Name {
					n.SetType(field.R)
					return
				}
			}
			v.errorf(n, "union type has no field %q", n.Field)
			return
		}
		structType, ok := subjectType.(types.StructType)
		if !ok {
			v.errorf(n, "cannot access field of non-struct type")
			return
		}
		fieldIndex := -1
		var fieldType types.Type
		for i, field := range structType.Fields {
			if field.L == n.Field.Name {
				fieldIndex = i
				fieldType = field.R
				break
			}
			if field.L == "" {
				if embedded, ok := field.R.(types.UnionType); ok {
					for _, unionField := range embedded.Fields {
						if unionField.L == n.Field.Name {
							fieldIndex = i
							fieldType = unionField.R
							break
						}
					}
				}
			}
			if fieldIndex != -1 {
				break
			}
		}
		if fieldIndex == -1 {
			v.errorf(n, "struct type has no field %q", n.Field)
			return
		}
		n.SetType(fieldType)

	case *parser.BlockNode:
		v.validateExpressionBlock(n, nil)

	case *parser.IfNode:
		v.validateIfExpression(n, nil)

	case *parser.MatchNode:
		v.validateMatch(n, nil)

	case *parser.SliceLiteralNode:
		for _, element := range n.Elements {
			v.validateValueExpr(element)
			if types.HasError(element.GetType()) {
				n.SetType(types.ErrorType{})
				return
			}
		}
		if n.RepeatValue != nil {
			v.validateValueExpr(n.RepeatValue)
			v.validateExpr(n.RepeatAmount)
			if anyErrorExpression(n.RepeatValue, n.RepeatAmount) {
				n.SetType(types.ErrorType{})
				return
			}
		}
		v.errorf(n, "cannot infer whether sequence literal is an array or slice; add a type annotation")
		if n.RepeatValue != nil {
			if _, noInit := n.RepeatValue.(*parser.NoInitializerNode); noInit {
				v.errorf(n, "an uninitialized repeated literal requires an expected array type")
			} else {
				v.validateValueExpr(n.RepeatValue)
			}
			n.RepeatAmount = v.validateExprWithExpected(n.RepeatAmount, types.PrimitiveUsz)
			n.SetType(types.ErrorType{})
			break
		}
		var common types.Type = nil

		if len(n.Elements) == 0 {
			v.errorf(n, "cannot infer element type of empty slice literal")
			n.SetType(types.ErrorType{})
			break
		}

		for _, el := range n.Elements {
			v.validateValueExpr(el)

			if common == nil {
				common = el.GetType()
			} else {
				common = types.CommonType(common, el.GetType())
				if types.HasError(common) {
					v.errorf(n, "slice element type mismatch")
					return
				}
			}
		}

		n.Type = types.ErrorType{}

		for i, el := range n.Elements {
			if !el.GetType().Equals(common) {
				n.Elements[i] = v.createCast(el, common)
			}
		}

	case *parser.StructLiteralNode:
		if n.Symbol != nil {
			v.validateStructLiteralWithExpected(n, n.Symbol.TypeInfo)
			break
		}
		if n.NoInitRemaining {
			v.errorf(n, "'---' in a struct literal requires a named or expected struct type")
			n.SetType(types.ErrorType{})
			break
		}

		fields := make([]shared.Pair[string, types.Type], 0, len(n.Fields))
		for i, field := range n.Fields {
			v.validateValueExpr(field.R)
			if types.HasError(field.R.GetType()) {
				n.SetType(types.ErrorType{})
				return
			}
			n.Fields[i].R = field.R
			fields = append(fields, shared.Pair[string, types.Type]{
				L: field.L,
				R: field.R.GetType(),
			})
		}

		n.SetType(types.StructType{Fields: fields})

	case *parser.IntegerLiteralNode,
		*parser.FloatLiteralNode,
		*parser.BoolLiteralNode,
		*parser.StringLiteralNode,
		*parser.EmbedNode,
		*parser.CStringLiteralNode,
		*parser.CharLiteralNode,
		*parser.NilLiteralNode:
		// nothing to validate

	case *parser.IdentifierNode:
		// Untyped compile-time integers remain untyped until a surrounding
		// ordinary expression supplies a concrete type.
		if n.Symbol != nil && !n.Symbol.InlineComptime && n.Symbol.Type != nil {
			n.SetType(n.Symbol.Type)
		}

	case *parser.NoInitializerNode:
		v.errorf(n, "'---' is only valid as a declaration initializer, a struct field initializer, or the final struct initializer entry")

	case *parser.SizeOfNode:
		if n.Expression != nil {
			v.validateExpr(n.Expression)
		}
		if types.HasError(n.OperandType) {
			n.SetType(types.ErrorType{})
			return
		}
		if types.Underlying(n.OperandType).Equals(types.PrimitiveVoid) {
			v.errorf(n, "@sizeof requires an object type, got void")
		} else if !types.IsComplete(n.OperandType) {
			v.errorf(n, "@sizeof requires a complete type, got %v", n.OperandType)
		}

	case *parser.SizeOfExprNode:
		v.validateExpr(n.Operand)
		if types.HasError(n.Operand.GetType()) || types.HasError(n.OperandType) {
			n.SetType(types.ErrorType{})
			return
		}
		if types.Underlying(n.OperandType).Equals(types.PrimitiveVoid) {
			v.errorf(n, "@sizeof requires an object expression, got void")
		} else if !types.IsComplete(n.OperandType) {
			v.errorf(n, "@sizeof requires a complete type, got %v", n.OperandType)
		}

	case *parser.AlignOfNode:
		if types.HasError(n.OperandType) {
			n.SetType(types.ErrorType{})
			return
		}
		if !types.IsComplete(n.OperandType) {
			v.errorf(n, "@alignof requires a complete type, got %v", n.OperandType)
			break
		}
		switch operand := types.Underlying(n.OperandType).(type) {
		case types.FunctionType:
			v.errorf(n, "@alignof requires an object type, got %v", n.OperandType)
		case types.PrimitiveType:
			if operand == types.PrimitiveVoid {
				v.errorf(n, "@alignof requires an object type, got void")
			}
		}

	case *parser.OffsetOfNode:
		if types.HasError(n.OperandType) {
			n.SetType(types.ErrorType{})
			return
		}
		operand := types.Underlying(n.OperandType)
		switch operand.(type) {
		case types.StructType, types.UnionType:
		default:
			v.errorf(n, "offsetof requires a struct or union type")
			return
		}
		if !hasOffsetField(operand, n.Field) {
			v.errorf(n, "%v has no field %q", n.OperandType, n.Field)
		}

	default:
		panic(fmt.Sprintf("unhandled expression type %T", n))
	}

}

func specializationConstant(expr parser.ExpressionNode) (symbols.SpecializationConstant, bool) {
	switch node := expr.(type) {
	case *parser.StringLiteralNode:
		if len(node.Value) > 256 {
			return symbols.SpecializationConstant{}, false
		}
		return symbols.SpecializationConstant{Kind: "str", Value: node.Value, Type: node.GetType()}, true
	case *parser.CStringLiteralNode:
		if len(node.Value) > 256 {
			return symbols.SpecializationConstant{}, false
		}
		return symbols.SpecializationConstant{Kind: "cstr", Value: node.Value, Type: node.GetType()}, true
	case *parser.BoolLiteralNode:
		return symbols.SpecializationConstant{Kind: "bool", Value: node.Value, Type: node.GetType()}, true
	case *parser.IntegerLiteralNode:
		return symbols.SpecializationConstant{Kind: "int", Value: node.Value, Type: node.GetType()}, true
	case *parser.FloatLiteralNode:
		return symbols.SpecializationConstant{Kind: "float", Value: node.Value, Type: node.GetType()}, true
	case *parser.CharLiteralNode:
		return symbols.SpecializationConstant{Kind: "char", Value: strconv.Itoa(int(node.Value)), Type: node.GetType()}, true
	default:
		return symbols.SpecializationConstant{}, false
	}
}

func (v *Validator) validateInlineAsm(n *parser.InlineAsmNode) {
	poisoned := false
	seenClobbers := make(map[string]struct{}, len(n.Clobbers))
	if strings.ContainsRune(n.Template, '\x00') {
		v.errorf(n, "@asm template cannot contain a NUL byte")
	}
	for i := range n.Outputs {
		output := &n.Outputs[i]
		if types.HasError(output.Type) {
			poisoned = true
		} else if !validInlineAsmType(output.Type) {
			v.errorf(n, "@asm output %d has unsupported type %v; use an integer, float, pointer, enum, or flags type", i+1, output.Type)
		}
		if output.Constraint == "" || output.Constraint[0] != '=' {
			v.errorf(n, "@asm output %d constraint must begin with '='", i+1)
		} else if strings.ContainsAny(output.Constraint, ",*") {
			v.errorf(n, "@asm output %d constraint cannot contain ',' or use an indirect '*' operand", i+1)
		} else if hasInlineAsmConstraintControl(output.Constraint) {
			v.errorf(n, "@asm output %d constraint contains whitespace or a control character", i+1)
		} else if !inlineAsmConstraintAcceptsType(strings.TrimPrefix(output.Constraint, "="), output.Type) {
			v.errorf(n, "@asm output %d constraint %q is incompatible with type %v", i+1, output.Constraint, output.Type)
		}
	}
	for i := range n.Inputs {
		input := &n.Inputs[i]
		v.validateExpr(input.Value)
		inputType := input.Value.GetType()
		if types.HasError(inputType) {
			poisoned = true
			continue
		}
		if !validInlineAsmType(inputType) {
			v.errorf(input.Value, "@asm input %d has unsupported type %v; use an integer, float, pointer, enum, or flags value", i+1, inputType)
		}
		if types.HasUntyped(inputType) {
			v.errorf(input.Value, "@asm input %d has an inferred numeric type; add an explicit cast", i+1)
		}
		if input.Constraint == "" {
			v.errorf(n, "@asm input %d constraint cannot be empty", i+1)
		} else if input.Constraint[0] == '=' || input.Constraint[0] == '~' || strings.Contains(input.Constraint, ",") {
			v.errorf(n, "invalid @asm input %d constraint %q", i+1, input.Constraint)
		} else if hasInlineAsmConstraintControl(input.Constraint) {
			v.errorf(n, "@asm input %d constraint contains whitespace or a control character", i+1)
		} else if tied, ok := inlineAsmTiedOutput(input.Constraint); ok {
			if tied >= len(n.Outputs) {
				v.errorf(n, "@asm input %d constraint refers to missing output %d", i+1, tied)
			} else if !inlineAsmTypesCompatible(inputType, n.Outputs[tied].Type) {
				v.errorf(n, "@asm input %d type %v is incompatible with tied output %d type %v", i+1, inputType, tied, n.Outputs[tied].Type)
			}
		} else if startsWithDecimalDigit(input.Constraint) {
			v.errorf(n, "invalid @asm tied input %d constraint %q", i+1, input.Constraint)
		} else if !inlineAsmConstraintAcceptsType(input.Constraint, inputType) {
			v.errorf(n, "@asm input %d constraint %q is incompatible with type %v", i+1, input.Constraint, inputType)
		} else if input.Constraint == "i" && !inlineAsmIntegerConstant(input.Value) {
			v.errorf(input.Value, "@asm input %d constraint \"i\" requires an integer constant", i+1)
		}
	}
	if poisoned {
		n.SetType(types.ErrorType{})
	}
	for _, clobber := range n.Clobbers {
		if clobber == "" || strings.ContainsAny(clobber, "{},~") || hasInlineAsmConstraintControl(clobber) || !inlineAsmClobberName(clobber) {
			v.errorf(n, "invalid @asm clobber name %q", clobber)
			continue
		}
		if _, duplicate := seenClobbers[clobber]; duplicate {
			v.errorf(n, "duplicate @asm clobber %q", clobber)
		}
		seenClobbers[clobber] = struct{}{}
	}
	for _, operand := range inlineAsmTemplateOperands(n.Template) {
		if operand >= len(n.Outputs)+len(n.Inputs) {
			v.errorf(n, "@asm template refers to missing operand %d", operand)
		}
	}
}

func hasInlineAsmConstraintControl(value string) bool {
	return strings.IndexFunc(value, func(r rune) bool { return r <= ' ' || r == 0x7f }) >= 0
}

func startsWithDecimalDigit(value string) bool {
	return value != "" && value[0] >= '0' && value[0] <= '9'
}

func inlineAsmTiedOutput(value string) (int, bool) {
	if !startsWithDecimalDigit(value) {
		return 0, false
	}
	for _, r := range value {
		if r < '0' || r > '9' {
			return 0, false
		}
	}
	index, err := strconv.Atoi(value)
	return index, err == nil
}

func inlineAsmConstraintAcceptsType(constraint string, t types.Type) bool {
	if constraint == "" || t == nil || types.HasError(t) {
		return true
	}
	underlying := types.Underlying(t)
	switch constraint {
	case "r":
		switch underlying.(type) {
		case types.PointerType, types.EnumType, types.FlagsType:
			return true
		case types.PrimitiveType:
			return !underlying.Equals(types.PrimitiveF32) && !underlying.Equals(types.PrimitiveF64) && !underlying.Equals(types.PrimitiveVoid)
		}
		return false
	case "f":
		return underlying.Equals(types.PrimitiveF32) || underlying.Equals(types.PrimitiveF64)
	case "i":
		switch underlying.(type) {
		case types.EnumType, types.FlagsType:
			return true
		case types.PrimitiveType:
			return !underlying.Equals(types.PrimitiveF32) && !underlying.Equals(types.PrimitiveF64) && !underlying.Equals(types.PrimitiveVoid)
		}
		return false
	default:
		return true // Target-specific constraints are validated by LLVM.
	}
}

func inlineAsmTypesCompatible(left, right types.Type) bool {
	return left != nil && right != nil && (left.Equals(right) || types.Underlying(left).Equals(types.Underlying(right)))
}

func inlineAsmIntegerConstant(value parser.ExpressionNode) bool {
	switch value.(type) {
	case *parser.IntegerLiteralNode, *parser.CharLiteralNode, *parser.BoolLiteralNode:
		return true
	default:
		return false
	}
}

func inlineAsmClobberName(value string) bool {
	if value == "" {
		return false
	}
	for _, r := range value {
		if !(r == '_' || r == '.' || r == '-' || r >= '0' && r <= '9' || r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z') {
			return false
		}
	}
	return true
}

func inlineAsmTemplateOperands(template string) []int {
	var result []int
	for i := 0; i < len(template); i++ {
		if template[i] != '$' || i+1 >= len(template) {
			continue
		}
		if template[i+1] == '$' {
			i++
			continue
		}
		start := i + 1
		if template[start] == '{' {
			start++
		}
		end := start
		for end < len(template) && template[end] >= '0' && template[end] <= '9' {
			end++
		}
		if end == start {
			continue
		}
		index, err := strconv.Atoi(template[start:end])
		if err == nil {
			result = append(result, index)
		}
		i = end - 1
	}
	return result
}

func validInlineAsmType(t types.Type) bool {
	if t == nil || types.HasUntyped(t) || types.HasTypeParameter(t) {
		return false
	}
	underlying := types.Underlying(t)
	switch underlying.(type) {
	case types.PrimitiveType, types.PointerType, types.EnumType, types.FlagsType:
		return !underlying.Equals(types.PrimitiveVoid)
	default:
		return false
	}
}

func (v *Validator) validateStaticTraitAssertion(node *parser.CastNode) {
	view := node.StaticTraitView
	source := node.Operand.GetType()
	if types.HasError(source) || types.HasError(view.Trait) {
		node.SetType(types.ErrorType{})
		return
	}
	pointer, _ := types.Underlying(source).(types.PointerType)
	_, _, sourceIsPointer, _ := methodOwnerIdentity(source)

	var concrete types.Type
	var probe types.PointerType
	if view.Access == types.TraitReceiverValue {
		if sourceIsPointer {
			concrete = pointer.Base
			probe = pointer
		} else {
			concrete = source
			probe = types.PointerType{Base: source}
		}
	} else if sourceIsPointer {
		if view.Access == types.TraitReceiverMutablePointer && !pointer.Mutable {
			v.errorf(node, "cannot reinterpret immutable %v as %v", source, view)
			return
		}
		concrete = pointer.Base
		probe = pointer
	} else {
		mutable := view.Access == types.TraitReceiverMutablePointer
		op := parser.UnaryOpReference
		if mutable {
			op = parser.UnaryOpMutableReference
		}
		reference := &parser.UnaryOpNode{Op: op, Operand: node.Operand, Loc: node.Operand.GetLoc(), Type: types.PointerType{Base: source, Mutable: mutable}}
		if !v.validateReferenceTarget(reference, node.Operand, mutable) {
			return
		}
		node.Operand = reference
		concrete = source
		probe = types.PointerType{Base: source, Mutable: mutable}
	}

	node.ConcreteType = concrete
	target := types.TraitPointerType{
		Trait:   view.Trait,
		Mutable: view.Access == types.TraitReceiverMutablePointer,
	}
	methods, conforms := v.analyser.structuralConformance(probe, target, node)
	node.AssertionMatches = conforms
	node.TraitMethods = methods
	if !conforms {
		v.errorf(node, "type %v does not conform to %v", concrete, view.Trait)
		node.SetType(types.ErrorType{})
	}
}

func (v *Validator) validatePointerArithmeticBase(node parser.Node, base types.Type) {
	if types.HasError(base) {
		return
	}
	underlying := types.Underlying(base)
	if underlying.Equals(types.PrimitiveVoid) {
		v.errorf(node, "cannot perform arithmetic on void pointer")
	} else if !types.IsComplete(base) {
		v.errorf(node, "cannot perform arithmetic on pointer to incomplete type %v", base)
	} else if _, function := underlying.(types.FunctionType); function {
		v.errorf(node, "cannot perform arithmetic on function pointer")
	}
}

func argumentWord(count int) string {
	if count == 1 {
		return "argument"
	}
	return "arguments"
}

func wasWere(count int) string {
	if count == 1 {
		return "was"
	}
	return "were"
}

func hasOffsetField(operand types.Type, name string) bool {
	var fields []shared.Pair[string, types.Type]
	switch operand := types.Underlying(operand).(type) {
	case types.StructType:
		fields = operand.Fields
	case types.UnionType:
		fields = operand.Fields
	default:
		return false
	}
	for _, field := range fields {
		if field.L == name || field.L == "" && hasOffsetField(field.R, name) {
			return true
		}
	}
	return false
}

func (v *Validator) validateReferenceTarget(node *parser.UnaryOpNode, target parser.ExpressionNode, mutable bool) bool {
	if target != nil && types.HasError(target.GetType()) {
		return false
	}
	if identifier := comptimeIdentifier(target); identifier != nil && identifier.Symbol != nil && identifier.Symbol.InlineComptime {
		v.errorf(node, "cannot take reference of untyped compile-time value %q", identifier.Name)
		return false
	}

	if mutable {
		return v.validateMutablePlace(target, true)
	}
	switch target := target.(type) {
	case *parser.IdentifierNode:
		if target.Symbol == nil || target.Symbol.Kind != symbols.SymbolKindVariable {
			v.errorf(node, "cannot take reference of this expression")
			return false
		}
		return true

	case *parser.UnaryOpNode:
		if target.Op != parser.UnaryOpDereference {
			v.errorf(node, "cannot take reference of this expression")
			return false
		}
		pointer, ok := types.Underlying(target.Operand.GetType()).(types.PointerType)
		if !ok || pointer.Base.Equals(types.PrimitiveVoid) {
			v.errorf(node, "cannot take reference of dereferenced void pointer")
			return false
		}
		return true

	case *parser.FieldAccessNode:
		if target.TaggedUnionType != nil {
			v.errorf(node, "cannot take reference of this expression")
			return false
		}
		if target.ResolvedIdentifier != nil {
			return v.validateReferenceTarget(node, target.ResolvedIdentifier, mutable)
		}
		return v.validateReferenceTarget(node, target.Subject, mutable)

	case *parser.IndexExprNode:
		switch subjectType := types.Underlying(target.Subject.GetType()).(type) {
		case types.SliceType:
			return v.validateReferenceTarget(node, target.Subject, mutable)
		case types.ArrayType:
			return v.validateReferenceTarget(node, target.Subject, mutable)
		case types.PointerType:
			if subjectType.Base.Equals(types.PrimitiveVoid) {
				v.errorf(node, "cannot take reference of index into void pointer")
				return false
			}
			return true
		default:
			v.errorf(node, "cannot take reference of this expression")
			return false
		}

	default:
		v.errorf(node, "cannot take reference of this expression")
		return false
	}
}

func (v *Validator) createCast(node parser.ExpressionNode, target types.Type) parser.ExpressionNode {
	if node.GetType().Equals(target) {
		return node
	}

	switch n := node.(type) {
	case *parser.IntegerLiteralNode:
		n.SetType(target)
		return n
	case *parser.FloatLiteralNode:
		n.SetType(target)
		return n
	}

	return &parser.CastNode{
		Operand: node,
		Type:    target,
	}
}

func integerLiteralFits(text string, target types.Type) bool {
	primitive, ok := types.Underlying(target).(types.PrimitiveType)
	if !ok {
		return true
	}
	bits := 0
	switch primitive {
	case types.PrimitiveI8, types.PrimitiveU8:
		bits = 8
	case types.PrimitiveI16, types.PrimitiveU16:
		bits = 16
	case types.PrimitiveI32, types.PrimitiveU32:
		bits = 32
	case types.PrimitiveI64, types.PrimitiveU64, types.PrimitiveIsz, types.PrimitiveUsz:
		bits = 64
	default:
		return true
	}
	value, ok := new(big.Int).SetString(text, 10)
	if !ok {
		return false
	}
	if types.IsUnsigned(primitive) {
		return value.Sign() >= 0 && value.BitLen() <= bits
	}
	limit := new(big.Int).Lsh(big.NewInt(1), uint(bits-1))
	minimum := new(big.Int).Neg(new(big.Int).Set(limit))
	maximum := new(big.Int).Sub(limit, big.NewInt(1))
	return value.Cmp(minimum) >= 0 && value.Cmp(maximum) <= 0
}

func negativeIntegerLiteralFits(text string, target types.Type) bool {
	primitive, ok := types.Underlying(target).(types.PrimitiveType)
	if !ok || !types.IsSigned(primitive) {
		return false
	}
	bits := 0
	switch primitive {
	case types.PrimitiveI8:
		bits = 8
	case types.PrimitiveI16:
		bits = 16
	case types.PrimitiveI32:
		bits = 32
	case types.PrimitiveI64, types.PrimitiveIsz:
		bits = 64
	default:
		return false
	}
	value, ok := new(big.Int).SetString(text, 10)
	if !ok || value.Sign() < 0 {
		return false
	}
	limit := new(big.Int).Lsh(big.NewInt(1), uint(bits-1))
	return value.Cmp(limit) <= 0
}

func (v *Validator) validateValueExpr(node parser.ExpressionNode) bool {
	v.validateExpr(node)
	return v.requireExpressionValue(node)
}

func hasVoidValue(t types.Type) bool {
	switch t := types.Underlying(t).(type) {
	case types.PrimitiveType:
		return t == types.PrimitiveVoid
	case types.SliceType:
		return hasVoidValue(t.Base)
	case types.ArrayType:
		return hasVoidValue(t.Base)
	case types.StructType:
		for _, field := range t.Fields {
			if hasVoidValue(field.R) {
				return true
			}
		}
	case types.UnionType:
		for _, field := range t.Fields {
			if hasVoidValue(field.R) {
				return true
			}
		}
	}
	return false
}

func (v *Validator) requireExpressionValue(node parser.ExpressionNode) bool {
	if node == nil || node.GetType() == nil {
		return false
	}
	if types.HasError(node.GetType()) {
		return false
	}
	if hasVoidValue(node.GetType()) {
		v.errorf(node, "void expression cannot be used as a value")
		return false
	}
	return true
}

func (v *Validator) validateExprWithExpected(node parser.ExpressionNode, expected types.Type) (result parser.ExpressionNode) {
	defer func() {
		if result != nil {
			v.requireExpressionValue(result)
		}
	}()
	if types.HasError(expected) {
		v.validateExpr(node)
		node.SetType(types.ErrorType{})
		return node
	}
	if lambda, ok := node.(*parser.LambdaNode); ok {
		v.prepareLambda(lambda, expected)
		v.validateExpr(lambda)
		return lambda
	}

	if identifier := comptimeIdentifier(node); identifier != nil && identifier.Symbol != nil &&
		identifier.Symbol.InlineComptime && types.IsUntyped(node.GetType()) && expected != nil &&
		!types.IsUntyped(expected) && node.GetType().CanCoerceTo(expected) {
		setComptimeReferenceType(node, expected)
		return node
	}

	if call, ok := node.(*parser.FunctionCallNode); ok {
		if !v.completeGenericCall(call, expected) {
			return call
		}
		if literal, shorthand := call.Callee.(*parser.EnumLiteralNode); shorthand {
			if info, tagged := types.TaggedUnion(expected); tagged {
				variant, index, exists := info.Variant(literal.Variant)
				if !exists {
					v.errorf(literal, "%s has no variant %q", expected, literal.Variant)
					call.TaggedUnionType = types.ErrorType{}
					return call
				}
				literal.Value = variant.TagValue
				literal.SetType(info.Tag)
				call.TaggedUnionType = expected
				call.TaggedUnionVariant = index
				v.validateExpr(call)
				return call
			}
		}
	}

	switch n := node.(type) {
	case *parser.StringLiteralNode:
		cstr := v.analyser.universe.Symbols["cstr"].TypeInfo
		if expected != nil && expected.Equals(cstr) {
			return &parser.CStringLiteralNode{
				Value: n.Value,
				Loc:   n.Loc,
				Type:  cstr,
			}
		}
	case *parser.NilLiteralNode:
		if types.IsPointer(expected) || isTraitPointerType(expected) {
			n.SetType(expected)
			return n
		}
	case *parser.EnumLiteralNode:
		var value string
		var exists bool
		switch t := types.Underlying(expected).(type) {
		case types.EnumType:
			value, exists = t.VariantValue(n.Variant)
		case types.FlagsType:
			value, exists = t.VariantValue(n.Variant)
		default:
			if info, tagged := types.TaggedUnion(expected); tagged {
				variant, index, variantExists := info.Variant(n.Variant)
				if !variantExists {
					v.errorf(n, "%s has no variant %q", expected, n.Variant)
					n.SetType(types.ErrorType{})
					return n
				}
				if len(variant.Fields) != 0 {
					v.errorf(n, "tagged union variant %q requires %d payload arguments", variant.Name, len(variant.Fields))
					n.SetType(types.ErrorType{})
					return n
				}
				n.Value = variant.TagValue
				n.SetType(info.Tag)
				constructor := &parser.FunctionCallNode{
					Callee: n, Loc: n.Loc, TaggedUnionType: expected, TaggedUnionVariant: index,
				}
				v.validateExpr(constructor)
				return constructor
			}
			v.errorf(n, "member shorthand .%s requires an expected enum or flags type", n.Variant)
			n.SetType(types.ErrorType{})
			return n
		}
		if !exists {
			v.errorf(n, "%s has no member %q", expected, n.Variant)
			n.SetType(types.ErrorType{})
			return n
		}
		n.Value = value
		n.SetType(expected)
		return n
	case *parser.IntegerLiteralNode:
		if types.IsNumeric(expected) {
			if types.IsInteger(expected) && !integerLiteralFits(n.Value, expected) {
				v.errorf(n, "integer literal %s is out of range for %v", n.Value, expected)
				n.SetType(types.ErrorType{})
				return n
			}
			n.SetType(expected)
			return n
		}
		n.SetType(types.UntypedInt{})
	case *parser.FloatLiteralNode:
		if types.IsFloat(expected) {
			n.SetType(expected)
			return n
		}
		n.SetType(types.UntypedFloat{})
	case *parser.UnaryOpNode:
		if (types.IsNumeric(expected) && n.Op == parser.UnaryOpNegate) ||
			((types.IsInteger(expected) || isFlagsType(expected)) && n.Op == parser.UnaryOpBitwiseNot) {
			if literal, ok := n.Operand.(*parser.IntegerLiteralNode); ok && n.Op == parser.UnaryOpNegate && types.IsInteger(expected) {
				if !negativeIntegerLiteralFits(literal.Value, expected) {
					v.errorf(n, "integer literal -%s is out of range for %v", literal.Value, expected)
					n.SetType(types.ErrorType{})
					return n
				}
				literal.SetType(expected)
				n.SetType(expected)
				return n
			}
			n.Operand = v.validateExprWithExpected(n.Operand, expected)
			if types.HasError(n.Operand.GetType()) {
				n.SetType(types.ErrorType{})
				return n
			}
			n.SetType(expected)
			return n
		}
	case *parser.BinaryOpNode:
		if isFlagsType(expected) && isBitwiseOperator(n.Op) {
			n.Operand1 = v.validateExprWithExpected(n.Operand1, expected)
			if n.Op == parser.BinaryOpShiftLeft || n.Op == parser.BinaryOpShiftRight {
				v.validateExpr(n.Operand2)
			} else {
				n.Operand2 = v.validateExprWithExpected(n.Operand2, expected)
			}
			if anyErrorExpression(n.Operand1, n.Operand2) {
				n.SetType(types.ErrorType{})
				return n
			}
			n.SetType(expected)
			return n
		}
		leftIsPointer := types.IsPointer(n.Operand1.GetType())
		rightIsPointer := types.IsPointer(n.Operand2.GetType())
		if types.IsNumeric(expected) && !leftIsPointer && !rightIsPointer &&
			(isArithmeticOperator(n.Op) || isBitwiseOperator(n.Op)) {
			n.Operand1 = v.validateExprWithExpected(n.Operand1, expected)
			n.Operand2 = v.validateExprWithExpected(n.Operand2, expected)
			if anyErrorExpression(n.Operand1, n.Operand2) {
				n.SetType(types.ErrorType{})
				return n
			}
			n.SetType(expected)
			return n
		}
	case *parser.BlockNode:
		v.validateExpressionBlock(n, expected)
		return n
	case *parser.IfNode:
		v.validateIfExpression(n, expected)
		return n
	case *parser.MatchNode:
		v.validateMatch(n, expected)
		return n
	case *parser.SliceExprNode:
		v.validateExpr(n)
		source, sourceIsSlice := types.Underlying(n.GetType()).(types.SliceType)
		target, targetIsPointer := types.Underlying(expected).(types.PointerType)
		if sourceIsSlice && targetIsPointer && (!target.Mutable || source.Mutable) &&
			(target.Base.Equals(types.PrimitiveVoid) || source.Base.Equals(target.Base)) {
			return &parser.CastNode{Operand: n, Loc: n.GetLoc(), Type: expected}
		}
	}

	if n, ok := node.(*parser.IdentifierNode); ok &&
		types.IsUntyped(n.GetType()) && !types.IsUntyped(expected) && n.GetType().CanCoerceTo(expected) {
		n.Symbol.Type = expected
		n.SetType(expected)
		return n
	}

	if n, ok := node.(*parser.StructLiteralNode); ok {
		v.validateStructLiteralWithExpected(n, expected)
		return n
	}

	if n, ok := node.(*parser.SliceLiteralNode); ok {
		v.validateSliceLiteralWithExpected(n, expected)
		return n
	}

	v.validateExpr(node)

	got := node.GetType()
	if types.HasError(got) {
		return node
	}
	// A value that already has the requested dynamic-trait representation must
	// not be treated as a concrete value and borrowed into a second trait
	// object. In particular, Any structurally accepts every pointer, including
	// a pointer to the storage of an existing *dyn Any value.
	if _, sourceIsTrait := v.analyser.traitPointer(got); sourceIsTrait {
		if _, targetIsTrait := v.analyser.traitPointer(expected); targetIsTrait && got.CanCoerceTo(expected) {
			if got.Equals(expected) {
				return node
			}
			return v.createCast(node, expected)
		}
	}
	if sourceArray, ok := types.Underlying(got).(types.ArrayType); ok {
		if targetSlice, ok := types.Underlying(expected).(types.SliceType); ok && sourceArray.Base.Equals(targetSlice.Base) {
			reference := &parser.UnaryOpNode{Operand: node, Loc: node.GetLoc()}
			if v.validateReferenceTarget(reference, node, targetSlice.Mutable) {
				return &parser.CastNode{Operand: node, Loc: node.GetLoc(), Type: expected}
			}
			return node
		}
	}
	if cast, ok := v.traitConversion(node, expected); ok {
		return cast
	}
	if target, ok := v.analyser.traitPointer(expected); ok {
		if types.HasUntyped(got) {
			v.errorf(node, "cannot infer concrete type for trait conversion; add a type annotation or cast")
			return node
		}
		if parameter, symbolic := genericTypeParameterBase(got); symbolic {
			constraint, constrained := types.Underlying(parameter.Constraint).(types.TraitType)
			if constrained && traitImplementsTrait(constraint, target.Trait) {
				if pointer, isPointer := types.Underlying(got).(types.PointerType); isPointer {
					if target.Mutable && !pointer.Mutable {
						v.errorf(node, "cannot use immutable %v as mutable %v", got, expected)
						return node
					}
					return &parser.CastNode{
						Operand: node, Loc: node.GetLoc(), Type: expected,
						TraitConversion: true, ConcreteType: pointer.Base,
					}
				}
				pointer := types.PointerType{Base: got, Mutable: target.Mutable}
				op := parser.UnaryOpReference
				if target.Mutable {
					op = parser.UnaryOpMutableReference
				}
				reference := &parser.UnaryOpNode{Op: op, Operand: node, Loc: node.GetLoc(), Type: pointer}
				if !target.Mutable || v.validateReferenceTarget(reference, node, true) {
					return &parser.CastNode{
						Operand: reference, Loc: node.GetLoc(), Type: expected,
						TraitConversion: true, ConcreteType: got,
					}
				}
				return node
			}
		}
		pointer := types.PointerType{Base: got, Mutable: target.Mutable}
		methods, conforms := v.analyser.structuralConformance(pointer, target, node)
		if conforms {
			op := parser.UnaryOpReference
			if target.Mutable {
				op = parser.UnaryOpMutableReference
			}
			reference := &parser.UnaryOpNode{Op: op, Operand: node, Loc: node.GetLoc(), Type: pointer}
			if !target.Mutable || v.validateReferenceTarget(reference, node, true) {
				return &parser.CastNode{Operand: reference, Loc: node.GetLoc(), Type: expected, TraitConversion: true, ConcreteType: got, TraitMethods: methods}
			}
			return node
		}
	}
	if !got.CanCoerceTo(expected) {
		v.errorf(node, "cannot use %v as %v", got, expected)
		return node
	}

	if !got.Equals(expected) {
		return v.createCast(node, expected)
	}

	return node
}

func (v *Validator) prepareLambda(n *parser.LambdaNode, expected types.Type) {
	if n.Attributed {
		if types.HasError(n.GetType()) {
			return
		}
		if context, ok := expectedLambdaFunction(expected); ok && n.Function != nil &&
			n.Function.Symbol != nil && len(context.Parameters) == len(n.Args) &&
			context.TypedVariadic == n.Function.Symbol.Signature.TypedVariadic {
			signature := n.Function.Symbol.Signature
			signature.ReturnType = context.ReturnType
			n.SetType(types.PointerType{Base: types.FunctionType{
				Parameters: signature.Parameters, ReturnType: signature.ReturnType,
				TypedVariadic: signature.TypedVariadic, VariadicElement: signature.VariadicElement,
			}})
		}
		return
	}
	attributor := v.analyser.NewAttributor()
	attributor.attributeLambda(n, expected, true)
	v.errors = append(v.errors, attributor.Errors()...)
}

func (v *Validator) completeGenericCall(call *parser.FunctionCallNode, expected types.Type) bool {
	if anyErrorExpression(call.Args...) {
		call.SetType(types.ErrorType{})
		return false
	}
	if call.Symbol == nil || call.Symbol.TemplateSymbol == nil {
		return true
	}
	template := call.Symbol.TemplateSymbol
	unresolved := false
	for i, argument := range call.Symbol.TypeArguments {
		if i < len(template.GenericParameters) && argument.Equals(template.GenericParameters[i]) {
			unresolved = true
			break
		}
	}
	if !unresolved {
		return true
	}

	var arguments []types.Type
	var err error
	if expected == nil {
		arguments, err = inferGenericArguments(
			template.GenericParameters, template.Signature.Parameters, expressionTypes(call.Args),
			template.Signature.TypedVariadic, call.VariadicExpansion,
		)
	} else {
		arguments, err = inferGenericArgumentsWithResult(
			template.GenericParameters, template.Signature.Parameters, expressionTypes(call.Args),
			template.Signature.ReturnType, expected,
			template.Signature.TypedVariadic, call.VariadicExpansion,
		)
	}
	if err != nil {
		v.errorf(call, "%v", err)
		call.SetType(types.ErrorType{})
		return false
	}
	arguments = preserveResolvedTypeArguments(template.GenericParameters, call.Symbol.TypeArguments, arguments)
	for i, argument := range arguments {
		if i < len(template.GenericParameters) && argument.Equals(template.GenericParameters[i]) {
			v.errorf(call, "cannot infer type argument %s", template.GenericParameters[i].Name)
			call.SetType(types.ErrorType{})
			return false
		}
	}

	beforeErrors := len(v.analyser.errors)
	if hasTypeParameters(arguments) {
		if v.analyser.checkGenericArguments(call, template.GenericParameters, arguments) {
			call.Symbol = dependentGenericFunctionSymbol(template, arguments)
		}
	} else if specialization := v.analyser.specializeGenericFunction(template, arguments, call); specialization != nil {
		call.Symbol = specialization.Symbol
	}
	if len(v.analyser.errors) > beforeErrors {
		v.errors = append(v.errors, v.analyser.errors[beforeErrors:]...)
		v.analyser.errors = v.analyser.errors[:beforeErrors]
		call.SetType(types.ErrorType{})
		return false
	}
	if call.Symbol == nil {
		call.SetType(types.ErrorType{})
		return false
	}
	if call.Name != nil {
		call.Name.Symbol = call.Symbol
	}
	if call.Symbol.Signature.ReturnType != nil {
		call.SetType(call.Symbol.Signature.ReturnType)
	} else {
		call.SetType(types.PrimitiveVoid)
	}
	return true
}

func comptimeIdentifier(node parser.ExpressionNode) *parser.IdentifierNode {
	switch n := node.(type) {
	case *parser.IdentifierNode:
		return n
	case *parser.FieldAccessNode:
		return n.ResolvedIdentifier
	default:
		return nil
	}
}

func setComptimeReferenceType(node parser.ExpressionNode, t types.Type) {
	node.SetType(t)
	if field, ok := node.(*parser.FieldAccessNode); ok {
		field.ResolvedIdentifier.SetType(t)
	}
}

func isBitwiseOperator(op parser.BinaryOpKind) bool {
	switch op {
	case parser.BinaryOpBitwiseAnd, parser.BinaryOpBitwiseXor, parser.BinaryOpBitwiseOr,
		parser.BinaryOpShiftLeft, parser.BinaryOpShiftRight:
		return true
	default:
		return false
	}
}

func isFlagsType(t types.Type) bool { _, ok := types.Underlying(t).(types.FlagsType); return ok }

func isArithmeticOperator(op parser.BinaryOpKind) bool {
	switch op {
	case parser.BinaryOpAdd,
		parser.BinaryOpSubtract,
		parser.BinaryOpMultiply,
		parser.BinaryOpDivide,
		parser.BinaryOpModulo:
		return true
	default:
		return false
	}
}

func (v *Validator) validateSliceLiteralWithExpected(n *parser.SliceLiteralNode, expected types.Type) {
	if types.HasError(expected) {
		n.SetType(types.ErrorType{})
		return
	}
	underlying := types.Underlying(expected)
	var elementType types.Type
	expectedLength := -1
	switch target := underlying.(type) {
	case types.SliceType:
		elementType = target.Base
	case types.ArrayType:
		elementType = target.Base
		expectedLength = target.Length
	default:
		v.validateExpr(n)
		return
	}
	if n.RepeatValue != nil {
		if noInit, ok := n.RepeatValue.(*parser.NoInitializerNode); ok {
			if expectedLength < 0 {
				v.errorf(n, "uninitialized repeated literal requires an array type")
			}
			noInit.SetType(elementType)
		} else {
			n.RepeatValue = v.validateExprWithExpected(n.RepeatValue, elementType)
		}
		n.RepeatAmount = v.validateExprWithExpected(n.RepeatAmount, types.PrimitiveUsz)
		if anyErrorExpression(n.RepeatValue, n.RepeatAmount) {
			n.SetType(types.ErrorType{})
			return
		}
		size := -1
		if amount, ok := staticIntegerValue(n.RepeatAmount); ok && amount.IsInt64() {
			size = int(amount.Int64())
		}
		if expectedLength >= 0 {
			if size < 0 {
				v.errorf(n, "array repetition count must be a compile-time integer")
			} else if size != expectedLength {
				v.errorf(n, "cannot assign repeated literal of length %d to %v", size, expected)
			}
		}
		n.SetType(expected)
		return
	}

	if expectedLength >= 0 && len(n.Elements) != expectedLength {
		v.errorf(n, "sequence literal has length %d, expected %v", len(n.Elements), expected)
	}

	for i, element := range n.Elements {
		n.Elements[i] = v.validateExprWithExpected(element, elementType)
	}
	if anyErrorExpression(n.Elements...) {
		n.SetType(types.ErrorType{})
		return
	}

	n.SetType(expected)
}

func staticIntegerValue(expr parser.ExpressionNode) (*big.Int, bool) {
	switch n := expr.(type) {
	case *parser.IntegerLiteralNode:
		base := 10
		if strings.HasPrefix(n.Value, "0x") || strings.HasPrefix(n.Value, "0X") ||
			strings.HasPrefix(n.Value, "0b") || strings.HasPrefix(n.Value, "0B") ||
			strings.HasPrefix(n.Value, "0o") || strings.HasPrefix(n.Value, "0O") {
			base = 0
		}
		value, ok := new(big.Int).SetString(n.Value, base)
		return value, ok
	case *parser.IdentifierNode:
		if n.Symbol == nil || !n.Symbol.InlineComptime || n.Symbol.ComptimeInteger == "" {
			return nil, false
		}
		value, ok := new(big.Int).SetString(n.Symbol.ComptimeInteger, 0)
		return value, ok
	case *parser.UnaryOpNode:
		if n.Op != parser.UnaryOpNegate {
			return nil, false
		}
		value, ok := staticIntegerValue(n.Operand)
		if !ok {
			return nil, false
		}
		return new(big.Int).Neg(value), true
	case *parser.CastNode:
		return staticIntegerValue(n.Operand)
	case *parser.BinaryOpNode:
		left, leftOK := staticIntegerValue(n.Operand1)
		right, rightOK := staticIntegerValue(n.Operand2)
		if !leftOK || !rightOK {
			return nil, false
		}
		result := new(big.Int)
		switch n.Op {
		case parser.BinaryOpAdd:
			return result.Add(left, right), true
		case parser.BinaryOpSubtract:
			return result.Sub(left, right), true
		case parser.BinaryOpMultiply:
			return result.Mul(left, right), true
		case parser.BinaryOpDivide:
			if right.Sign() == 0 {
				return nil, false
			}
			return result.Quo(left, right), true
		case parser.BinaryOpModulo:
			if right.Sign() == 0 {
				return nil, false
			}
			return result.Rem(left, right), true
		case parser.BinaryOpBitwiseAnd:
			return result.And(left, right), true
		case parser.BinaryOpBitwiseOr:
			return result.Or(left, right), true
		case parser.BinaryOpBitwiseXor:
			return result.Xor(left, right), true
		case parser.BinaryOpShiftLeft, parser.BinaryOpShiftRight:
			if !right.IsUint64() {
				return nil, false
			}
			if n.Op == parser.BinaryOpShiftLeft {
				return result.Lsh(left, uint(right.Uint64())), true
			}
			return result.Rsh(left, uint(right.Uint64())), true
		}
		return nil, false
	default:
		return nil, false
	}
}

func (v *Validator) validateStructLiteralWithExpected(n *parser.StructLiteralNode, expected types.Type) {
	if types.HasError(expected) {
		n.SetType(types.ErrorType{})
		return
	}
	if flagType, ok := types.Underlying(expected).(types.FlagsType); ok {
		if n.NoInitRemaining {
			v.errorf(n, "flags literals cannot use '---'")
			n.SetType(types.ErrorType{})
			return
		}
		if len(n.Fields) != 0 {
			v.errorf(n, "flags literals use '.member' entries")
			n.SetType(types.ErrorType{})
			return
		}
		seen := map[string]struct{}{}
		for _, member := range n.FlagMembers {
			if _, duplicate := seen[member]; duplicate {
				v.errorf(n, "duplicate flag member %q", member)
				continue
			}
			seen[member] = struct{}{}
			if _, exists := flagType.VariantValue(member); !exists {
				v.errorf(n, "unknown flag member %q", member)
			}
		}
		n.SetType(expected)
		return
	}
	if len(n.FlagMembers) != 0 {
		v.errorf(n, "only flags literals may use '.member' entries")
		n.SetType(types.ErrorType{})
		return
	}
	if unionType, ok := types.Underlying(expected).(types.UnionType); ok {
		if n.NoInitRemaining && len(n.Fields) == 0 {
			n.SetType(expected)
			return
		}
		if len(n.Fields) != 1 {
			v.errorf(n, "union literal must initialize exactly one field")
			n.SetType(types.ErrorType{})
			return
		}
		field := n.Fields[0]
		for _, unionField := range unionType.Fields {
			if unionField.L == field.L {
				if noInit, ok := field.R.(*parser.NoInitializerNode); ok {
					noInit.SetType(unionField.R)
				} else {
					n.Fields[0].R = v.validateExprWithExpected(field.R, unionField.R)
				}
				if types.HasError(n.Fields[0].R.GetType()) {
					n.SetType(types.ErrorType{})
					return
				}
				n.SetType(expected)
				return
			}
		}
		v.errorf(n, "unknown field %q in union literal", field.L)
		n.SetType(types.ErrorType{})
		return
	}
	structType, ok := types.Underlying(expected).(types.StructType)
	if !ok {
		v.errorf(n, "cannot use struct literal for non-struct type %v", expected)
		n.SetType(types.ErrorType{})
		return
	}
	if structType.TaggedUnion != nil {
		v.errorf(n, "tagged union values must be constructed with %v.Variant(...) syntax", expected)
		n.SetType(types.ErrorType{})
		return
	}

	fieldTypes := make(map[string]types.Type, len(structType.Fields))
	for _, field := range structType.Fields {
		if field.L == "" {
			if embedded, ok := field.R.(types.UnionType); ok {
				for _, unionField := range embedded.Fields {
					fieldTypes[unionField.L] = unionField.R
				}
			}
			continue
		}
		fieldTypes[field.L] = field.R
	}

	seen := make(map[string]struct{}, len(n.Fields))
	poisoned := false
	for i, field := range n.Fields {
		expectedFieldType, exists := fieldTypes[field.L]
		if !exists {
			v.errorf(n, "unknown field %q in struct literal", field.L)
			continue
		}

		if _, dup := seen[field.L]; dup {
			v.errorf(n, "duplicate field %q in struct literal", field.L)
			continue
		}
		seen[field.L] = struct{}{}

		if noInit, ok := field.R.(*parser.NoInitializerNode); ok {
			noInit.SetType(expectedFieldType)
		} else {
			n.Fields[i].R = v.validateExprWithExpected(field.R, expectedFieldType)
			poisoned = poisoned || types.HasError(n.Fields[i].R.GetType())
		}
	}

	missingFields := []string{}
	for _, field := range structType.Fields {
		if field.L == "" {
			if embedded, ok := field.R.(types.UnionType); ok {
				initialized := 0
				for _, unionField := range embedded.Fields {
					if _, ok := seen[unionField.L]; ok {
						initialized++
					}
				}
				if initialized == 0 {
					missingFields = append(missingFields, "<embedded union>")
				} else if initialized > 1 {
					v.errorf(n, "embedded union must initialize exactly one field")
				}
			}
			continue
		}
		if _, ok := seen[field.L]; !ok {
			missingFields = append(missingFields, field.L)
		}
	}
	if len(missingFields) > 0 && !n.NoInitRemaining {
		v.errorf(n, "missing fields in struct literal: %v", strings.Join(missingFields, ", "))
	}

	if poisoned {
		n.SetType(types.ErrorType{})
	} else {
		n.SetType(expected)
	}
}