package sema
import (
"fmt"
"os"
"path/filepath"
"strconv"
"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 Attributor struct {
analyser *Analyser
errors []error
templatesOnly bool
functionState map[*symbols.Symbol]functionAttributionState
}
func (a *Analyser) NewAttributor() *Attributor {
return &Attributor{
analyser: a,
functionState: make(map[*symbols.Symbol]functionAttributionState),
}
}
type functionDefinitionInfo struct {
node *parser.FunctionDefNode
module string
}
type functionAttributionState uint8
const (
functionUnattributed functionAttributionState = iota
functionAttributing
functionAttributed
)
func (a *Attributor) errorf(node parser.Node, format string, args ...any) {
a.errors = append(a.errors, shared.NewError(node.GetLoc(), format, args...))
}
func (a *Attributor) AttributeModule(root *parser.RootNode) {
a.templatesOnly = false
a.selectModule(root)
a.attributeNode(root)
}
func (a *Attributor) AttributeGenericTemplates(root *parser.RootNode) {
a.templatesOnly = true
a.selectModule(root)
for _, node := range root.Body {
function, ok := node.(*parser.FunctionDefNode)
if !ok || !function.IsGeneric() {
continue
}
bindings := a.analyser.templateBindings(function.Symbol)
a.analyser.withDefinitionContext(
a.analyser.currentMod,
a.analyser.currentTrustedStandardLibrary,
bindings,
func() { a.attributeNode(function) },
)
}
a.templatesOnly = false
}
func (a *Attributor) selectModule(root *parser.RootNode) {
path := a.analyser.modulePaths[root]
a.analyser.currentMod = path
if mod := a.analyser.modules[path]; mod != nil {
a.analyser.current = mod.Scope
a.analyser.currentTrustedStandardLibrary = mod.TrustedStandardLibrary
}
a.analyser.currentImports = a.analyser.importsByModule[path]
a.analyser.currentImportAliases = a.analyser.importAliasesByModule[path]
a.analyser.aliases = a.analyser.aliasesByModule[path]
}
func (a *Attributor) Errors() []error {
return a.errors
}
func (a *Attributor) attributeNode(node parser.Node) {
switch n := node.(type) {
case *parser.RootNode:
for _, stmt := range n.Body {
if function, ok := stmt.(*parser.FunctionDefNode); ok && function.IsGeneric() {
if !a.templatesOnly {
continue
}
} else if a.templatesOnly {
continue
}
a.attributeNode(stmt)
}
case *parser.ImportNode, *parser.ModuleNode:
// pass
case *parser.TypeAliasNode:
// pass
case *parser.FunctionDefNode:
if n.GenericInstance {
break
}
if a.functionState[n.Symbol] != functionUnattributed {
break
}
a.functionState[n.Symbol] = functionAttributing
defer func() { a.functionState[n.Symbol] = functionAttributed }()
restoreSpecialization := a.enterSpecialization(n.Symbol)
defer restoreSpecialization()
for _, arg := range n.Args {
if arg.Default != nil {
a.attributeExpr(arg.Default)
}
}
if n.Body != nil {
if n.ExpressionBody {
a.attributeExpr(n.Body.(parser.ExpressionNode))
} else {
a.attributeNode(n.Body)
}
}
foreignAttr := n.Attributes.Get(attributes.AttributeTypeForeign)
if foreignAttr != nil && n.RetTypeNode == nil {
a.errorf(n, "foreign function must have a return type annotation")
}
if n.Symbol.Signature.ReturnType == nil {
var candidates []returnTypeCandidate
for _, ret := range collectFunctionReturnNodesFromNode(n.Body) {
if ret.ReturnValue == nil {
candidates = append(candidates, returnTypeCandidate{node: ret, ty: types.PrimitiveVoid})
} else {
candidates = append(candidates, returnTypeCandidate{node: ret, ty: ret.ReturnValue.GetType()})
}
}
if n.ExpressionBody && parser.NodeFallsThrough(n.Body) {
body := n.Body.(parser.ExpressionNode)
candidates = append(candidates, returnTypeCandidate{node: n, ty: body.GetType()})
}
n.Symbol.Signature.ReturnType = a.mergeReturnTypes(candidates, "inconsistent return types")
if types.HasUntyped(n.Symbol.Signature.ReturnType) {
a.errorf(n, "cannot infer function return type from untyped numeric value; add a return type annotation or cast")
n.Symbol.Signature.ReturnType = types.ErrorType{}
}
}
case *parser.BlockNode:
a.attributeBlock(n)
case *parser.DeclarationNode:
if len(n.GenericParameters) != 0 {
break
}
restoreSpecialization := a.enterSpecialization(n.Symbol)
defer restoreSpecialization()
if n.Value != nil {
a.attributeExpr(n.Value)
}
if n.Name == "_" {
break
}
if n.Value != nil && n.Symbol.Type == nil {
n.Symbol.Type = n.Value.GetType()
}
// Ordinary runtime declarations cannot retain an untyped numeric or
// unresolved enum type. Mark the symbol erroneous immediately so later
// attribution silently poisons dependent expressions; validation still
// reports the primary diagnostic at this declaration.
if n.Symbol.Type != nil && types.HasUntyped(n.Symbol.Type) && !n.Symbol.InlineComptime {
n.Symbol.Type = types.ErrorType{}
}
if n.Value != nil && n.Symbol.GenericOrigin == nil {
n.Symbol.GenericOrigin = genericExpressionOrigin(n.Value)
}
if n.Value != nil && n.Symbol.StaticTraitView == nil {
n.Symbol.StaticTraitView = staticTraitView(n.Value)
}
case *parser.MultiDeclarationNode:
if cast, ok := n.Value.(*parser.CastNode); ok {
cast.Checked = true
}
a.attributeExpr(n.Value)
if result, ok := n.Value.GetType().(types.MultipleReturnType); ok {
for i, sym := range n.Symbols {
if sym != nil && i < len(result.Types) {
sym.Type = result.Types[i]
}
}
}
if len(n.Symbols) != 0 && n.Symbols[0] != nil {
n.Symbols[0].StaticTraitView = staticTraitView(n.Value)
}
case *parser.AssignmentNode:
if len(n.Assignees) > 0 {
if cast, ok := n.Value.(*parser.CastNode); ok {
cast.Checked = true
}
}
if len(n.Assignees) > 0 {
for _, target := range n.Assignees {
if id, ok := target.(*parser.IdentifierNode); !ok || id.Name != "_" {
a.attributeExpr(target)
}
}
} else {
if id, ok := n.Assignee.(*parser.IdentifierNode); !ok || id.Name != "_" {
a.attributeExpr(n.Assignee)
}
}
a.attributeExpr(n.Value)
case *parser.IfNode:
a.attributeIf(n)
case *parser.MatchNode:
a.attributeMatch(n)
case *parser.ForNode:
for _, node := range n.ExprsOrStmts {
a.attributeNode(node)
}
a.attributeNode(n.Body)
case *parser.RangeForNode:
a.attributeExpr(n.Start)
a.attributeExpr(n.End)
if n.Symbol != nil {
n.Symbol.Type = types.PrimitiveUsz
}
a.attributeNode(n.Body)
case *parser.ForEachNode:
a.attributeExpr(n.Iterable)
var elementType types.Type
switch iterable := types.Underlying(n.Iterable.GetType()).(type) {
case types.SliceType:
elementType = iterable.Base
case types.ArrayType:
elementType = iterable.Base
}
if n.Symbol != nil && elementType != nil {
n.Symbol.Type = forEachElementType(n, elementType)
}
if len(n.Destructure) != 0 && elementType != nil {
componentTypes, valid := forEachDestructureTypes(elementType)
if valid {
for i := range n.Destructure {
if n.Destructure[i].Symbol != nil && i < len(componentTypes) {
n.Destructure[i].Symbol.Type = componentTypes[i]
}
}
}
}
if n.IndexSymbol != nil {
n.IndexSymbol.Type = types.PrimitiveUsz
}
a.attributeNode(n.Body)
case *parser.ControlKeywordNode:
for _, value := range n.ReturnValues {
a.attributeExpr(value)
}
case *parser.DeferNode:
a.attributeNode(n.Action)
case parser.ExpressionNode:
a.attributeExpr(n)
default:
panic(fmt.Sprintf("unexpected node type: %T\n", node))
}
}
func (a *Attributor) attributeExpr(node parser.ExpressionNode) {
switch n := node.(type) {
case *parser.LambdaNode:
a.attributeLambda(n, nil, false)
case *parser.IdentifierNode:
if n.Symbol != nil && n.Symbol.TemplateSymbol != nil && !a.templatesOnly && !hasTypeParameters(n.Symbol.TypeArguments) {
if specialization := a.attributeGenericSpecialization(n.Symbol.TemplateSymbol, n.Symbol.TypeArguments, n); specialization != nil {
n.Symbol = specialization.Symbol
}
}
if n.Symbol != nil {
a.attributeFunctionDefinition(n.Symbol)
}
if n.Symbol != nil && n.Symbol.Template {
if len(n.ResolvedTypeArgs) == 0 {
a.errorf(n, "parameterized binding %q requires type arguments", n.Symbol.Name)
n.SetType(types.ErrorType{})
} else if a.templatesOnly || hasTypeParameters(n.ResolvedTypeArgs) {
if a.analyser.checkGenericArguments(n, n.Symbol.GenericParameters, n.ResolvedTypeArgs) {
n.Symbol = dependentGenericFunctionSymbol(n.Symbol, n.ResolvedTypeArgs)
} else {
n.SetType(types.ErrorType{})
}
} else if specialization := a.attributeGenericSpecialization(n.Symbol, n.ResolvedTypeArgs, n); specialization != nil {
n.Symbol = specialization.Symbol
}
}
if n.Symbol != nil && n.Symbol.Template {
n.SetType(types.ErrorType{})
} else if n.Symbol != nil && n.Symbol.Kind == symbols.SymbolKindFunction && n.Symbol.Signature != nil {
a.attributeFunctionDefinition(n.Symbol)
ret := n.Symbol.Signature.ReturnType
if ret == nil {
ret = types.PrimitiveVoid
}
n.SetType(types.PointerType{Base: types.FunctionType{Parameters: n.Symbol.Signature.Parameters, ReturnType: ret, TypedVariadic: n.Symbol.Signature.TypedVariadic, VariadicElement: n.Symbol.Signature.VariadicElement}})
} else if n.Symbol == nil || n.Symbol.Type == nil {
a.errorf(n, "undefined identifier: %s", n.String())
n.SetType(types.ErrorType{})
} else {
n.SetType(n.Symbol.Type)
}
case *parser.IntegerLiteralNode:
n.SetType(types.UntypedInt{})
case *parser.FloatLiteralNode:
n.SetType(types.UntypedFloat{})
case *parser.BoolLiteralNode:
n.SetType(types.PrimitiveBool)
case *parser.StringLiteralNode:
n.SetType(a.analyser.universe.Symbols["str"].TypeInfo)
case *parser.EmbedNode:
n.SetType(types.SliceType{Base: types.PrimitiveU8})
if filepath.IsAbs(n.Path) {
a.errorf(n, "@embed path must be relative to the source file")
break
}
path := filepath.Join(filepath.Dir(n.Loc.FilePath), filepath.FromSlash(n.Path))
contents, err := os.ReadFile(path)
if err != nil {
a.errorf(n, "cannot embed %q: %v", n.Path, err)
break
}
n.Contents = string(contents)
case *parser.CStringLiteralNode:
n.SetType(a.analyser.universe.Symbols["cstr"].TypeInfo)
case *parser.CharLiteralNode:
n.SetType(types.PrimitiveU8)
case *parser.NilLiteralNode:
n.SetType(types.PointerType{
Base: types.PrimitiveVoid,
Mutable: false,
})
case *parser.NoInitializerNode:
n.SetType(types.NoInitializerType{})
case *parser.EnumLiteralNode:
// Leading-dot enum literals are resolved later from an expected type.
n.SetType(types.UnresolvedEnum{})
case *parser.StructLiteralNode:
for _, field := range n.Fields {
a.attributeExpr(field.R)
}
for _, field := range n.Fields {
if types.HasError(field.R.GetType()) {
n.SetType(types.ErrorType{})
break
}
}
if types.HasError(n.GetType()) {
break
}
if n.Symbol != nil {
n.SetType(n.Symbol.TypeInfo)
} else {
fields := make([]shared.Pair[string, types.Type], 0, len(n.Fields))
for _, field := range n.Fields {
fields = append(fields, shared.Pair[string, types.Type]{
L: field.L,
R: field.R.GetType(),
})
}
n.SetType(types.StructType{Fields: fields})
}
case *parser.SliceLiteralNode:
if n.RepeatValue != nil {
a.attributeExpr(n.RepeatValue)
a.attributeExpr(n.RepeatAmount)
if anyErrorExpression(n.RepeatValue, n.RepeatAmount) {
n.SetType(types.ErrorType{})
break
}
size := -1
if amount, ok := n.RepeatAmount.(*parser.IntegerLiteralNode); ok {
if parsed, err := strconv.Atoi(amount.Value); err == nil {
size = parsed
}
}
n.SetType(types.SequenceType{Base: n.RepeatValue.GetType(), Length: size})
break
}
typs := []types.Type{}
for _, elem := range n.Elements {
a.attributeExpr(elem)
if types.HasError(elem.GetType()) {
n.SetType(types.ErrorType{})
}
typs = append(typs, elem.GetType())
}
if types.HasError(n.GetType()) {
break
}
if len(typs) > 0 {
currentType := typs[0]
for _, t := range typs[1:] {
got := types.CommonType(currentType, t)
if types.HasError(got) {
a.errorf(n, "inconsistent slice element types: expected %v, got %v", currentType, t)
}
currentType = got
}
n.SetType(types.SequenceType{Base: currentType, Length: len(n.Elements)})
} else {
n.SetType(types.SequenceType{Base: types.PrimitiveVoid, Length: 0})
}
case *parser.InlineAsmNode:
for _, output := range n.Outputs {
if types.HasError(output.Type) {
n.SetType(types.ErrorType{})
break
}
}
for _, input := range n.Inputs {
a.attributeExpr(input.Value)
if types.HasError(input.Value.GetType()) {
n.SetType(types.ErrorType{})
}
}
if types.HasError(n.GetType()) {
break
}
switch len(n.Outputs) {
case 0:
n.SetType(types.PrimitiveVoid)
case 1:
n.SetType(n.Outputs[0].Type)
default:
result := make([]types.Type, len(n.Outputs))
for i := range n.Outputs {
result[i] = n.Outputs[i].Type
}
n.SetType(types.MultipleReturnType{Types: result})
}
case *parser.FunctionCallNode:
if n.CompileTimeApplication != nil {
if n.CompileTimeApplication.Symbol.Kind == symbols.SymbolKindVariable {
a.attributeExpr(n.CompileTimeApplication)
n.SetType(n.CompileTimeApplication.GetType())
}
break
}
if n.TaggedUnionType != nil {
for _, arg := range n.Args {
a.attributeExpr(arg)
}
if types.HasError(n.TaggedUnionType) || anyErrorExpression(n.Args...) {
n.SetType(types.ErrorType{})
break
}
if n.TaggedUnionTemplate != nil {
info, tagged := types.TaggedUnion(n.TaggedUnionType)
if !tagged || n.TaggedUnionVariant < 0 || n.TaggedUnionVariant >= len(info.Variants) {
a.errorf(n, "invalid tagged union constructor")
n.TaggedUnionType = types.ErrorType{}
break
}
variant := info.Variants[n.TaggedUnionVariant]
if len(n.Args) != len(variant.Fields) {
break
}
patterns := make([]types.Type, len(variant.Fields))
for i := range variant.Fields {
patterns[i] = variant.Fields[i].R
}
generic := a.analyser.genericAliases[n.TaggedUnionTemplate]
arguments, err := inferGenericArguments(
generic.parameters, patterns, expressionTypes(n.Args), false, false,
)
if err != nil {
a.errorf(n, "%v", err)
n.TaggedUnionType = types.ErrorType{}
break
}
specialization := a.analyser.specializeGenericAlias(generic, arguments, n, false)
if specialization == nil {
n.TaggedUnionType = types.ErrorType{}
break
}
n.TaggedUnionType = specialization.TypeInfo
n.TaggedUnionTemplate = nil
}
n.SetType(n.TaggedUnionType)
break
}
methodHandled := false
if n.Symbol == nil {
methodHandled = a.attributeMethodCall(n)
}
if n.Symbol == nil && !methodHandled {
a.attributeExpr(n.Callee)
if member, ok := n.Callee.(*parser.FieldAccessNode); ok && member.MethodSymbol != nil && member.MethodSymbol.StaticMethod {
n.Symbol = member.MethodSymbol
n.Name = &parser.IdentifierNode{
Name: member.MethodSymbol.Name,
Symbol: member.MethodSymbol,
Loc: member.Loc,
}
if member.MethodModule != a.analyser.currentMod {
n.Name.Module = member.MethodModule
n.Name.ResolvedModuleName = member.MethodModule
}
}
}
if n.Symbol != nil && n.Symbol.Template {
arguments, remaining, explicit, valid := a.analyser.explicitCallTypeArguments(n.Symbol.GenericParameters, n.Symbol.Signature, n.Args, false, n)
if !valid {
n.SetType(types.ErrorType{})
break
}
if explicit {
n.Args = remaining
n.Symbol = dependentGenericFunctionSymbol(n.Symbol, arguments)
if n.Name != nil {
n.Name.Symbol = n.Symbol
n.Name.ResolvedTypeArgs = arguments
}
}
}
if n.Symbol != nil {
a.attributeFunctionDefinition(n.Symbol)
}
if n.Symbol != nil && n.Symbol.Signature.ReturnType != nil {
n.SetType(n.Symbol.Signature.ReturnType)
} else if n.Symbol != nil {
n.SetType(types.PrimitiveVoid)
}
for _, arg := range n.Args {
a.attributeExpr(arg)
}
if (n.Symbol == nil && anyErrorExpression(n.Callee)) ||
(n.Symbol != nil && hasErrorSignature(n.Symbol.Signature)) ||
anyErrorExpression(n.Args...) {
n.SetType(types.ErrorType{})
break
}
if n.Symbol != nil && n.Symbol.Template {
template := n.Symbol
arguments := []types.Type(nil)
var err error
if n.Name != nil && len(n.Name.ResolvedTypeArgs) != 0 {
arguments, err = inferGenericArgumentsPartial(
template.GenericParameters, template.Signature.Parameters, expressionTypes(n.Args),
template.Signature.TypedVariadic, n.VariadicExpansion,
)
arguments = preserveResolvedTypeArguments(template.GenericParameters, n.Name.ResolvedTypeArgs, arguments)
} else {
arguments, err = inferGenericArgumentsPartial(
template.GenericParameters, template.Signature.Parameters, expressionTypes(n.Args),
template.Signature.TypedVariadic, n.VariadicExpansion,
)
}
if err != nil {
a.errorf(n, "%v", err)
n.SetType(types.ErrorType{})
} else if a.templatesOnly || hasTypeParameters(arguments) {
if a.analyser.checkGenericArguments(n, template.GenericParameters, arguments) {
n.Symbol = dependentGenericFunctionSymbol(template, arguments)
if n.Name != nil {
n.Name.Symbol = n.Symbol
}
} else {
n.SetType(types.ErrorType{})
}
} else if a.analyser.genericFunctions[template] == nil {
if a.analyser.checkGenericArguments(n, template.GenericParameters, arguments) {
n.Symbol = dependentGenericFunctionSymbol(template, arguments)
if n.Name != nil {
n.Name.Symbol = n.Symbol
}
} else {
n.SetType(types.ErrorType{})
}
} else if specialization := a.attributeGenericSpecialization(template, arguments, n); specialization != nil {
n.Symbol = specialization.Symbol
if n.Name != nil {
n.Name.Symbol = specialization.Symbol
}
}
} else if n.Symbol != nil && n.Symbol.TemplateSymbol != nil && !a.templatesOnly && !hasTypeParameters(n.Symbol.TypeArguments) {
template := n.Symbol.TemplateSymbol
if specialization := a.attributeGenericSpecialization(template, n.Symbol.TypeArguments, n); specialization != nil {
n.Symbol = specialization.Symbol
if n.Name != nil {
n.Name.Symbol = specialization.Symbol
}
}
}
if n.Symbol != nil {
a.attributeFunctionDefinition(n.Symbol)
if n.Symbol.Signature.ReturnType != nil {
n.SetType(n.Symbol.Signature.ReturnType)
} else {
n.SetType(types.PrimitiveVoid)
}
}
case *parser.BlockNode:
a.attributeBlock(n)
case *parser.IfNode:
a.attributeIf(n)
case *parser.MatchNode:
a.attributeMatch(n)
case *parser.UnaryOpNode:
a.attributeExpr(n.Operand)
if types.HasError(n.Operand.GetType()) {
n.SetType(types.ErrorType{})
break
}
switch n.Op {
case parser.UnaryOpNegate:
if types.IsSigned(n.Operand.GetType()) || types.IsFloat(n.Operand.GetType()) {
n.SetType(n.Operand.GetType())
} else {
a.errorf(n, "cannot apply negation operator to non-numeric type: %v", n.Operand.GetType())
n.SetType(types.ErrorType{})
}
case parser.UnaryOpLogicalNot:
if n.Operand.GetType().Equals(types.PrimitiveBool) {
n.SetType(types.PrimitiveBool)
} else {
a.errorf(n, "cannot apply logical not operator to non-boolean type: %v", n.Operand.GetType())
n.SetType(types.ErrorType{})
}
case parser.UnaryOpReference:
n.SetType(types.PointerType{
Base: n.Operand.GetType(),
Mutable: false,
})
case parser.UnaryOpMutableReference:
n.SetType(types.PointerType{
Base: n.Operand.GetType(),
Mutable: true,
})
case parser.UnaryOpDereference:
if ptr, ok := types.Underlying(n.Operand.GetType()).(types.PointerType); ok {
n.SetType(ptr.Base)
} else {
a.errorf(n, "cannot dereference non-pointer type: %v", n.Operand.GetType())
n.SetType(types.ErrorType{})
}
case parser.UnaryOpSliceLen:
switch types.Underlying(n.Operand.GetType()).(type) {
case types.SliceType, types.ArrayType:
n.SetType(types.PrimitiveUsz)
default:
a.errorf(n, "cannot get length of non-array-or-slice type: %v", n.Operand.GetType())
n.SetType(types.ErrorType{})
}
case parser.UnaryOpBitwiseNot:
if types.IsInteger(n.Operand.GetType()) {
n.SetType(n.Operand.GetType())
} else if _, ok := types.Underlying(n.Operand.GetType()).(types.FlagsType); ok {
n.SetType(n.Operand.GetType())
} else {
a.errorf(n, "cannot apply bitwise not operator to non-integer type: %v", n.Operand.GetType())
n.SetType(types.ErrorType{})
}
default:
panic("unhandled unary operator")
}
case *parser.IndexExprNode:
a.attributeExpr(n.Subject)
a.attributeExpr(n.Index)
if anyErrorExpression(n.Subject, n.Index) {
n.SetType(types.ErrorType{})
break
}
switch t := types.Underlying(n.Subject.GetType()).(type) {
case types.SliceType:
n.SetType(t.Base)
case types.ArrayType:
n.SetType(t.Base)
case types.PointerType:
n.SetType(t.Base)
default:
a.errorf(n, "cannot index type %v", n.Subject.GetType())
n.SetType(types.ErrorType{})
}
switch n.Index.GetType().(type) {
case types.UntypedInt:
n.Index = &parser.CastNode{
Operand: n.Index,
Type: types.PrimitiveUsz,
}
case types.UntypedFloat:
a.errorf(n, "cannot use untyped float as index; cast to integer type")
n.SetType(types.ErrorType{})
}
case *parser.SliceExprNode:
a.attributeExpr(n.Subject)
if n.Start != nil {
a.attributeExpr(n.Start)
}
if n.End != nil {
a.attributeExpr(n.End)
}
if anyErrorExpression(n.Subject, n.Start, n.End) {
n.SetType(types.ErrorType{})
break
}
switch t := types.Underlying(n.Subject.GetType()).(type) {
case types.SliceType:
result := types.Type(types.SliceType{Base: t.Base, Mutable: t.Mutable})
if defined, ok := n.Subject.GetType().(types.DefinedType); ok && defined.Module == "" && defined.Name == "str" {
result = defined
}
n.SetType(result)
case types.PointerType:
n.SetType(types.SliceType{Base: t.Base, Mutable: t.Mutable})
case types.ArrayType:
n.SetType(types.SliceType{Base: t.Base, Mutable: mutableArrayPlace(n.Subject)})
default:
a.errorf(n, "cannot slice type %v", n.Subject.GetType())
n.SetType(types.ErrorType{})
}
normalizeBound := func(bound *parser.ExpressionNode) {
if *bound == nil {
return
}
switch (*bound).GetType().(type) {
case types.UntypedInt:
*bound = &parser.CastNode{
Operand: *bound,
Type: types.PrimitiveUsz,
}
case types.UntypedFloat:
a.errorf(n, "cannot use untyped float as slice bound; cast to integer type")
n.SetType(types.ErrorType{})
}
}
normalizeBound(&n.Start)
normalizeBound(&n.End)
case *parser.FieldAccessNode:
if n.ResolvedIdentifier != nil {
ident := n.ResolvedIdentifier
if ident.Symbol.TemplateSymbol != nil && !a.templatesOnly && !hasTypeParameters(ident.Symbol.TypeArguments) {
if specialization := a.attributeGenericSpecialization(ident.Symbol.TemplateSymbol, ident.Symbol.TypeArguments, ident); specialization != nil {
ident.Symbol = specialization.Symbol
}
}
a.attributeFunctionDefinition(ident.Symbol)
if ident.Symbol.Template {
if len(ident.ResolvedTypeArgs) == 0 {
a.errorf(n, "parameterized binding %q requires type arguments", ident.Symbol.Name)
n.SetType(types.ErrorType{})
break
}
if a.templatesOnly || hasTypeParameters(ident.ResolvedTypeArgs) {
if !a.analyser.checkGenericArguments(ident, ident.Symbol.GenericParameters, ident.ResolvedTypeArgs) {
n.SetType(types.ErrorType{})
break
}
ident.Symbol = dependentGenericFunctionSymbol(ident.Symbol, ident.ResolvedTypeArgs)
} else if specialization := a.attributeGenericSpecialization(ident.Symbol, ident.ResolvedTypeArgs, ident); specialization != nil {
ident.Symbol = specialization.Symbol
}
}
switch ident.Symbol.Kind {
case symbols.SymbolKindVariable:
ident.SetType(ident.Symbol.Type)
case symbols.SymbolKindFunction:
a.attributeFunctionDefinition(ident.Symbol)
ret := ident.Symbol.Signature.ReturnType
if ret == nil {
ret = types.PrimitiveVoid
}
ident.SetType(types.PointerType{Base: types.FunctionType{Parameters: ident.Symbol.Signature.Parameters, ReturnType: ret, TypedVariadic: ident.Symbol.Signature.TypedVariadic, VariadicElement: ident.Symbol.Signature.VariadicElement}})
case symbols.SymbolKindType:
ident.SetType(ident.Symbol.TypeInfo)
}
n.SetType(ident.GetType())
break
}
if n.ModulePath != "" {
break
}
if a.attributeMethodValue(n) {
break
}
if ident := resolvedTypeIdentifier(n.Subject); ident != nil {
if types.HasError(ident.Symbol.TypeInfo) {
n.SetType(types.ErrorType{})
break
}
if info, tagged := types.TaggedUnion(ident.Symbol.TypeInfo); tagged {
variant, index, exists := info.Variant(n.Field.Name)
if !exists {
a.errorf(n, "tagged union %v has no variant %q", ident.Symbol.TypeInfo, n.Field.Name)
n.SetType(types.ErrorType{})
} else if len(variant.Fields) != 0 {
a.errorf(n, "tagged union variant %q requires %d payload arguments", variant.Name, len(variant.Fields))
n.SetType(types.ErrorType{})
} else {
n.TaggedUnionType = ident.Symbol.TypeInfo
n.TaggedUnionVariant = index
n.SetType(ident.Symbol.TypeInfo)
}
break
}
if enumType, ok := types.Underlying(ident.Symbol.TypeInfo).(types.EnumType); ok {
ident.SetType(enumType)
value, exists := enumType.VariantValue(n.Field.Name)
if !exists {
a.errorf(n, "enum %s has no variant %q", enumType, n.Field.Name)
n.SetType(types.ErrorType{})
} else {
n.IsEnumValue = true
n.EnumValue = value
n.SetType(ident.Symbol.TypeInfo)
}
break
}
if flagType, ok := types.Underlying(ident.Symbol.TypeInfo).(types.FlagsType); ok {
ident.SetType(flagType)
value, exists := flagType.VariantValue(n.Field.Name)
if !exists {
a.errorf(n, "flags %s has no member %q", ident.Symbol.TypeInfo, n.Field.Name)
n.SetType(types.ErrorType{})
} else {
n.IsFlagValue, n.FlagValue, n.FlagType = true, value, ident.Symbol.TypeInfo
n.SetType(ident.Symbol.TypeInfo)
}
break
}
}
if ident, ok := n.Subject.(*parser.IdentifierNode); ok &&
ident.Symbol != nil && ident.Symbol.Kind == symbols.SymbolKindModule {
a.errorf(n, "cannot resolve module-qualified name %s.%s", ident.Name, n.Field.Name)
n.SetType(types.ErrorType{})
break
}
a.attributeExpr(n.Subject)
if types.HasError(n.Subject.GetType()) {
n.SetType(types.ErrorType{})
break
}
subjectType := types.Underlying(n.Subject.GetType())
if ptr, ok := subjectType.(types.PointerType); ok {
subjectType = types.Underlying(ptr.Base)
}
if info, tagged := types.TaggedUnion(subjectType); tagged && info.Auto && n.Field.Name == "tag" {
a.errorf(n, "the tag of an @auto tagged union is compiler-private")
n.SetType(types.ErrorType{})
break
}
switch t := subjectType.(type) {
case types.FlagsType:
value, exists := t.VariantValue(n.Field.Name)
if !exists {
a.errorf(n, "flags %v has no member %q", n.Subject.GetType(), n.Field.Name)
n.SetType(types.ErrorType{})
} else if value == "0" {
a.errorf(n, "zero-valued flag %q cannot be used as a boolean field", n.Field.Name)
n.SetType(types.ErrorType{})
} else {
n.IsFlagTest, n.FlagValue, n.FlagType = true, value, n.Subject.GetType()
n.SetType(types.PrimitiveBool)
}
case types.StructType:
found := false
for _, field := range t.Fields {
if field.L == n.Field.Name {
n.SetType(field.R)
found = true
break
}
if field.L == "" {
if embedded, ok := field.R.(types.UnionType); ok {
for _, unionField := range embedded.Fields {
if unionField.L == n.Field.Name {
n.SetType(unionField.R)
found = true
break
}
}
}
}
if found {
break
}
}
if !found {
a.errorf(n, "type %v does not have a field named %q", fieldOwnerDisplayType(n.Subject.GetType()), n.Field.Name)
n.SetType(types.ErrorType{})
}
case types.UnionType:
found := false
for _, field := range t.Fields {
if field.L == n.Field.Name {
n.SetType(field.R)
found = true
break
}
}
if !found {
a.errorf(n, "type %v does not have a field named %q", fieldOwnerDisplayType(n.Subject.GetType()), n.Field.Name)
n.SetType(types.ErrorType{})
}
default:
a.errorf(n, "cannot access field of type %v", n.Subject.GetType())
n.SetType(types.ErrorType{})
}
case *parser.BinaryOpNode:
a.attributeExpr(n.Operand1)
a.attributeExpr(n.Operand2)
if anyErrorExpression(n.Operand1, n.Operand2) {
n.SetType(types.ErrorType{})
break
}
if literal, ok := n.Operand1.(*parser.EnumLiteralNode); ok && isUnresolvedEnum(literal.GetType()) {
if isEnumOrFlags(n.Operand2.GetType()) {
a.resolveEnumLiteral(literal, n.Operand2.GetType())
}
}
if literal, ok := n.Operand2.(*parser.EnumLiteralNode); ok && isUnresolvedEnum(literal.GetType()) {
if isEnumOrFlags(n.Operand1.GetType()) {
a.resolveEnumLiteral(literal, n.Operand1.GetType())
}
}
for _, operand := range []parser.ExpressionNode{n.Operand1, n.Operand2} {
if literal, ok := operand.(*parser.EnumLiteralNode); ok && isUnresolvedEnum(literal.GetType()) {
a.errorf(literal, "cannot infer enum type for .%s", literal.Variant)
literal.SetType(types.ErrorType{})
}
}
t1 := n.Operand1.GetType()
t2 := n.Operand2.GetType()
switch n.Op {
case parser.BinaryOpAdd, parser.BinaryOpSubtract, parser.BinaryOpMultiply, parser.BinaryOpDivide, parser.BinaryOpModulo:
p1, pointer1 := types.Underlying(t1).(types.PointerType)
p2, pointer2 := types.Underlying(t2).(types.PointerType)
if n.Op == parser.BinaryOpAdd && pointer1 && types.IsInteger(t2) {
n.SetType(t1)
} else if n.Op == parser.BinaryOpAdd && types.IsInteger(t1) && pointer2 {
n.SetType(t2)
} else if n.Op == parser.BinaryOpSubtract && pointer1 && types.IsInteger(t2) {
n.SetType(t1)
} else if n.Op == parser.BinaryOpSubtract && pointer1 && pointer2 && p1.Base.Equals(p2.Base) {
n.SetType(types.PrimitiveIsz)
} else if types.IsNumeric(t1) && types.IsNumeric(t2) {
got := types.PromoteNumeric(t1, t2)
if types.HasError(got) {
a.errorf(n, "incompatible types for binary operator: %v and %v", t1, t2)
n.SetType(types.ErrorType{})
} else {
n.SetType(got)
}
} else {
a.errorf(n, "cannot apply arithmetic operator to non-numeric types: %v and %v", t1, t2)
n.SetType(types.ErrorType{})
}
case parser.BinaryOpEqual, parser.BinaryOpNotEqual, parser.BinaryOpLess, parser.BinaryOpLessEqual, parser.BinaryOpGreater, parser.BinaryOpGreaterEqual:
if types.IsNumeric(t1) && types.IsNumeric(t2) {
n.SetType(types.PrimitiveBool)
} else if (n.Op == parser.BinaryOpEqual || n.Op == parser.BinaryOpNotEqual) &&
((isNilLiteral(n.Operand1) && isTraitPointerType(t2)) ||
(isTraitPointerType(t1) && isNilLiteral(n.Operand2))) {
n.SetType(types.PrimitiveBool)
} else if t1.Equals(t2) {
n.SetType(types.PrimitiveBool)
} else if types.IsPointer(t1) && types.IsPointer(t2) {
n.SetType(types.PrimitiveBool)
} else {
a.errorf(n, "cannot compare values of types %v and %v", t1, t2)
n.SetType(types.ErrorType{})
}
case parser.BinaryOpLogicalAnd, parser.BinaryOpLogicalOr:
if t1.Equals(types.PrimitiveBool) && t2.Equals(types.PrimitiveBool) {
n.SetType(types.PrimitiveBool)
} else {
a.errorf(n, "cannot apply logical operator to non-boolean types: %v and %v", t1, t2)
n.SetType(types.ErrorType{})
}
case parser.BinaryOpBitwiseAnd, parser.BinaryOpBitwiseXor, parser.BinaryOpBitwiseOr,
parser.BinaryOpShiftLeft, parser.BinaryOpShiftRight:
if _, leftFlags := types.Underlying(t1).(types.FlagsType); leftFlags {
if (n.Op == parser.BinaryOpShiftLeft || n.Op == parser.BinaryOpShiftRight) && types.IsInteger(t2) {
n.SetType(t1)
} else if t1.Equals(t2) {
n.SetType(t1)
} else {
a.errorf(n, "flags bitwise operands must have the same type")
n.SetType(types.ErrorType{})
}
break
}
if types.IsInteger(t1) && types.IsInteger(t2) {
got := types.PromoteNumeric(t1, t2)
if types.HasError(got) {
a.errorf(n, "incompatible integer types for bitwise operator: %v and %v", t1, t2)
n.SetType(types.ErrorType{})
} else {
n.SetType(got)
}
} else {
a.errorf(n, "cannot apply bitwise operator to non-integer types: %v and %v", t1, t2)
n.SetType(types.ErrorType{})
}
default:
panic("unhandled binary operator")
}
case *parser.CastNode:
a.attributeExpr(n.Operand)
n.GenericAssertion = genericExpressionOrigin(n.Operand) != nil
var target types.Type
view := n.StaticTraitView
if n.ToType != nil {
target, view = a.analyser.resolveCastTarget(n.ToType)
} else {
target = n.Type
if n.Checked && n.CheckedType != nil {
target = n.CheckedType
}
if target == nil {
a.errorf(n, "cast is missing target type")
target = types.ErrorType{}
}
}
if view != nil {
n.StaticTraitView = view
target = n.Operand.GetType()
if view.Access != types.TraitReceiverValue {
_, _, targetIsPointer, _ := methodOwnerIdentity(target)
if pointer, ok := types.Underlying(target).(types.PointerType); ok && targetIsPointer {
target = types.PointerType{Base: pointer.Base, Mutable: view.Access == types.TraitReceiverMutablePointer}
} else {
target = types.PointerType{Base: target, Mutable: view.Access == types.TraitReceiverMutablePointer}
}
}
}
n.CheckedType = target
if types.HasError(n.Operand.GetType()) || types.HasError(target) {
n.SetType(types.ErrorType{})
break
}
if n.Checked {
n.SetType(types.MultipleReturnType{Types: []types.Type{target, types.PrimitiveBool}})
} else {
n.SetType(target)
}
case *parser.ReprNode:
a.attributeExpr(n.Operand)
if types.HasError(n.Operand.GetType()) {
n.SetType(types.ErrorType{})
break
}
operand := n.Operand.GetType()
if pointer, ok := types.Underlying(operand).(types.PointerType); ok {
if pointer.Mutable {
a.errorf(n, "@repr does not permit mutable tagged union pointers")
n.SetType(types.ErrorType{})
break
}
repr, tagged := types.TaggedUnionRepr(pointer.Base)
if !tagged {
a.errorf(n, "@repr requires an explicitly tagged union value or immutable pointer, got %v", operand)
n.SetType(types.ErrorType{})
break
}
n.SetType(types.PointerType{Base: repr})
break
}
repr, tagged := types.TaggedUnionRepr(operand)
if !tagged {
a.errorf(n, "@repr requires an explicitly tagged union value or immutable pointer, got %v", operand)
n.SetType(types.ErrorType{})
break
}
n.SetType(repr)
case *parser.SizeOfNode:
if n.Expression != nil {
a.attributeExpr(n.Expression)
if types.HasError(n.Expression.GetType()) {
n.SetType(types.ErrorType{})
n.OperandType = types.ErrorType{}
break
}
n.OperandType = n.Expression.GetType()
} else {
n.OperandType = a.analyser.resolveTypeNode(n.Operand)
}
if types.HasError(n.OperandType) {
n.SetType(types.ErrorType{})
break
}
n.SetType(types.PrimitiveUsz)
case *parser.SizeOfExprNode:
a.attributeExpr(n.Operand)
if types.HasError(n.Operand.GetType()) {
n.SetType(types.ErrorType{})
n.OperandType = types.ErrorType{}
break
}
n.SetType(types.PrimitiveUsz)
n.OperandType = n.Operand.GetType()
case *parser.AlignOfNode:
if n.Expression != nil {
a.attributeExpr(n.Expression)
if types.HasError(n.Expression.GetType()) {
n.SetType(types.ErrorType{})
n.OperandType = types.ErrorType{}
break
}
n.OperandType = n.Expression.GetType()
} else {
n.OperandType = a.analyser.resolveTypeNode(n.Operand)
}
if types.HasError(n.OperandType) {
n.SetType(types.ErrorType{})
break
}
n.SetType(types.PrimitiveUsz)
case *parser.OffsetOfNode:
n.OperandType = a.analyser.resolveTypeNode(n.Operand)
if types.HasError(n.OperandType) {
n.SetType(types.ErrorType{})
break
}
n.SetType(types.PrimitiveUsz)
default:
panic(fmt.Sprintf("unexpected expression type: %T\n", node))
}
if node.GetType() == nil {
panic(fmt.Sprintf("expression without type: %T at %v", node, node.GetLoc()))
}
}
func expectedLambdaFunction(expected types.Type) (types.FunctionType, bool) {
pointer, ok := types.Underlying(expected).(types.PointerType)
if !ok {
return types.FunctionType{}, false
}
function, ok := types.Underlying(pointer.Base).(types.FunctionType)
return function, ok
}
func (a *Attributor) attributeLambda(n *parser.LambdaNode, expected types.Type, requireComplete bool) {
if n.Attributed || n.Function == nil || n.Function.Symbol == nil {
return
}
signature := n.Function.Symbol.Signature
context, hasContext := expectedLambdaFunction(expected)
if !hasContext {
for _, parameter := range signature.Parameters {
if parameter == nil {
// Contextual typing is applied by validation at the containing
// declaration, assignment, return, or call site.
if !requireComplete {
return
}
break
}
}
}
if hasContext && len(context.Parameters) != len(n.Args) {
a.errorf(n, "lambda has %d parameters, but contextual function type has %d", len(n.Args), len(context.Parameters))
hasContext = false
}
if hasContext && n.TypedVariadic && !context.TypedVariadic {
a.errorf(n, "typed variadic lambda requires a typed variadic contextual function type")
hasContext = false
}
if hasContext && !n.TypedVariadic && context.TypedVariadic {
n.TypedVariadic = true
n.Function.TypedVariadic = true
signature.TypedVariadic = true
}
missing := false
for i, arg := range n.Args {
if signature.Parameters[i] == nil {
if hasContext {
signature.Parameters[i] = context.Parameters[i]
} else {
a.errorf(arg, "cannot infer type of lambda parameter %q; add a type annotation", arg.Name)
signature.Parameters[i] = types.ErrorType{}
missing = true
}
}
if arg.Symbol != nil {
arg.Symbol.Type = signature.Parameters[i]
}
}
if signature.TypedVariadic && len(signature.Parameters) != 0 {
if slice, ok := types.Underlying(signature.Parameters[len(signature.Parameters)-1]).(types.SliceType); ok {
signature.VariadicElement = slice.Base
} else {
a.errorf(n.Args[len(n.Args)-1], "typed variadic lambda parameter must have a slice type")
missing = true
}
}
if hasContext {
signature.ReturnType = context.ReturnType
}
a.attributeExpr(n.Body)
if signature.ReturnType == nil {
var candidates []returnTypeCandidate
for _, ret := range collectFunctionReturnNodesFromNode(n.Body) {
if ret.ReturnValue == nil {
candidates = append(candidates, returnTypeCandidate{node: ret, ty: types.PrimitiveVoid})
} else {
candidates = append(candidates, returnTypeCandidate{node: ret, ty: ret.ReturnValue.GetType()})
}
}
if parser.NodeFallsThrough(n.Body) {
candidates = append(candidates, returnTypeCandidate{node: n, ty: n.Body.GetType()})
}
signature.ReturnType = a.mergeReturnTypes(candidates, "inconsistent return types")
if types.HasUntyped(signature.ReturnType) {
if !hasContext && !requireComplete {
signature.ReturnType = nil
n.SetType(types.ErrorType{})
return
}
a.errorf(n, "cannot infer lambda return type from untyped numeric value; provide a contextual function type or cast")
signature.ReturnType = types.ErrorType{}
missing = true
}
}
functionType := types.FunctionType{
Parameters: signature.Parameters, ReturnType: signature.ReturnType,
TypedVariadic: signature.TypedVariadic, VariadicElement: signature.VariadicElement,
}
if missing {
n.SetType(types.ErrorType{})
} else {
n.SetType(types.PointerType{Base: functionType})
}
n.Attributed = true
}
func mutableArrayPlace(expr parser.ExpressionNode) bool {
switch n := expr.(type) {
case *parser.IdentifierNode:
return n.Symbol != nil && n.Symbol.Mutable
case *parser.FieldAccessNode:
if n.ResolvedIdentifier != nil {
return mutableArrayPlace(n.ResolvedIdentifier)
}
return mutableArrayPlace(n.Subject)
case *parser.IndexExprNode:
return mutableArrayPlace(n.Subject)
case *parser.UnaryOpNode:
if n.Op == parser.UnaryOpDereference {
pointer, ok := types.Underlying(n.Operand.GetType()).(types.PointerType)
return ok && pointer.Mutable
}
}
return false
}
func isNilLiteral(node parser.ExpressionNode) bool {
_, ok := node.(*parser.NilLiteralNode)
return ok
}
func isTraitPointerType(t types.Type) bool {
_, ok := types.Underlying(t).(types.TraitPointerType)
return ok
}
func genericExpressionOrigin(node parser.ExpressionNode) types.Type {
switch n := node.(type) {
case *parser.IdentifierNode:
if n.Symbol != nil {
return n.Symbol.GenericOrigin
}
case *parser.UnaryOpNode:
origin := genericExpressionOrigin(n.Operand)
if origin == nil {
return nil
}
switch n.Op {
case parser.UnaryOpReference:
return types.PointerType{Base: origin}
case parser.UnaryOpMutableReference:
return types.PointerType{Base: origin, Mutable: true}
case parser.UnaryOpDereference:
if pointer, ok := types.Underlying(origin).(types.PointerType); ok {
return pointer.Base
}
}
case *parser.FunctionCallNode:
if n.Symbol != nil && n.Symbol.TemplateSymbol != nil {
origin := n.Symbol.TemplateSymbol.Signature.ReturnType
if types.HasTypeParameter(origin) {
// A concrete specialization has already classified its return type.
// Only dependent calls need to defer cast classification until the
// enclosing generic definition is specialized.
if !types.HasTypeParameter(n.Symbol.Signature.ReturnType) {
return nil
}
return origin
}
}
}
return nil
}
func staticTraitView(node parser.ExpressionNode) *types.StaticTraitView {
switch n := node.(type) {
case *parser.CastNode:
return n.StaticTraitView
case *parser.IdentifierNode:
if n.Symbol != nil {
return n.Symbol.StaticTraitView
}
case *parser.UnaryOpNode:
view := staticTraitView(n.Operand)
if view == nil {
return nil
}
result := *view
switch n.Op {
case parser.UnaryOpReference:
result.Access = types.TraitReceiverPointer
case parser.UnaryOpMutableReference:
result.Access = types.TraitReceiverMutablePointer
case parser.UnaryOpDereference:
result.Access = types.TraitReceiverValue
default:
return nil
}
return &result
}
return staticTraitViewForGenericType(node.GetType())
}
func fieldOwnerDisplayType(t types.Type) types.Type {
// Preserve a nominal pointer type's name in diagnostics. Only peel an
// actual pointer expression to describe the type whose fields were queried.
if ptr, ok := t.(types.PointerType); ok {
return ptr.Base
}
return t
}
func (a *Attributor) attributeMethodValue(n *parser.FieldAccessNode) bool {
ident := resolvedTypeIdentifier(n.Subject)
if ident == nil {
return false
}
module, owner := "", ""
var ownerArguments []types.Type
if ident.Symbol.Template && ident.Symbol.Kind == symbols.SymbolKindType {
module, owner = ident.Symbol.DefinitionModule, ident.Symbol.Name
} else {
var ok bool
module, owner, _, ok = methodOwnerIdentity(ident.Symbol.TypeInfo)
if !ok {
return false
}
if ident.Symbol.Kind == symbols.SymbolKindType {
if ident.Symbol.TemplateSymbol != nil {
ownerArguments = ident.Symbol.TypeArguments
} else if defined, specialized := ident.Symbol.TypeInfo.(types.DefinedType); specialized && defined.GenericName != "" {
// A transparent alias to a generic nominal type is not itself a
// template symbol. Recover the concrete owner arguments retained by
// its target so static generic methods specialize as they would on
// the underlying type spelling.
ownerArguments = defined.TypeArguments
}
}
}
method := a.analyser.methods[module+":"+owner][n.Field.Name]
if method == nil {
return false
}
if method.DefinitionModule != a.analyser.currentMod && !method.Public {
a.errorf(n, "method %q is not public", n.Field.Name)
n.SetType(types.ErrorType{})
return true
}
a.attributeFunctionDefinition(method)
if method.Template && (len(ownerArguments) != 0 || len(n.Field.TypeArguments) != 0) {
arguments := append([]types.Type(nil), ownerArguments...)
arguments = append(arguments, a.analyser.resolveGenericArguments(n.Field.TypeArguments)...)
if a.templatesOnly || hasTypeParameters(arguments) {
if !a.analyser.checkGenericArguments(n, method.GenericParameters, arguments) {
n.SetType(types.ErrorType{})
return true
}
method = dependentGenericFunctionSymbol(method, arguments)
} else {
symbol := a.analyser.specializeGenericFunctionSymbol(method, arguments, n)
if symbol == nil {
n.SetType(types.ErrorType{})
return true
}
method = symbol
}
}
ret := method.Signature.ReturnType
if ret == nil {
ret = types.PrimitiveVoid
}
n.MethodSymbol = method
n.MethodModule = method.DefinitionModule
n.SetType(types.PointerType{Base: types.FunctionType{Parameters: method.Signature.Parameters, ReturnType: ret, TypedVariadic: method.Signature.TypedVariadic, VariadicElement: method.Signature.VariadicElement}})
return true
}
func (a *Attributor) attributeFunctionDefinition(symbol *symbols.Symbol) {
if symbol == nil || symbol.Signature == nil || symbol.Signature.ReturnType != nil || a.functionState[symbol] != functionUnattributed {
return
}
definition := a.analyser.functionDefinitions[symbol]
if definition == nil {
return
}
mod := a.analyser.modules[definition.module]
if mod == nil {
return
}
bindings := map[string]types.Type(nil)
if definition.node.IsGeneric() {
bindings = make(map[string]types.Type, len(symbol.GenericParameters))
for _, parameter := range symbol.GenericParameters {
bindings[parameter.Name] = parameter
}
}
previousTemplatesOnly := a.templatesOnly
a.templatesOnly = definition.node.IsGeneric()
defer func() { a.templatesOnly = previousTemplatesOnly }()
a.analyser.withDefinitionContext(definition.module, mod.TrustedStandardLibrary, bindings, func() {
a.attributeNode(definition.node)
})
}
func resolvedTypeIdentifier(expr parser.ExpressionNode) *parser.IdentifierNode {
if ident, ok := expr.(*parser.IdentifierNode); ok && ident.Symbol != nil && ident.Symbol.Kind == symbols.SymbolKindType {
return ident
}
if field, ok := expr.(*parser.FieldAccessNode); ok && field.ResolvedIdentifier != nil && field.ResolvedIdentifier.Symbol.Kind == symbols.SymbolKindType {
return field.ResolvedIdentifier
}
if call, ok := expr.(*parser.FunctionCallNode); ok && call.CompileTimeApplication != nil && call.CompileTimeApplication.Symbol.Kind == symbols.SymbolKindType {
return call.CompileTimeApplication
}
return nil
}
func (a *Attributor) attributeMethodCall(n *parser.FunctionCallNode) bool {
member, ok := n.Callee.(*parser.FieldAccessNode)
if !ok {
return false
}
if ident, ok := member.Subject.(*parser.IdentifierNode); ok && ident.Symbol != nil && ident.Symbol.Kind == symbols.SymbolKindType {
return false
}
a.attributeExpr(member.Subject)
if types.HasError(member.Subject.GetType()) {
n.SetType(types.ErrorType{})
return true
}
if resolvedTypeIdentifier(member.Subject) != nil {
return false
}
if view := staticTraitView(member.Subject); view != nil {
return a.attributeStaticTraitMethodCall(n, member, view)
}
if traitPtr, ok := a.analyser.traitPointer(member.Subject.GetType()); ok {
member.Subject.SetType(traitPtr)
for slot, requirement := range traitPtr.Trait.Methods {
if requirement.Name != member.Field.Name {
continue
}
if requirement.Receiver == types.TraitReceiverMutablePointer && !traitPtr.Mutable {
a.errorf(n, "method %q requires mutable trait access", requirement.Name)
n.SetType(types.ErrorType{})
return true
}
if len(requirement.GenericParameters) != 0 {
a.errorf(n, "parameterized trait method %q cannot be called dynamically", requirement.Name)
n.SetType(types.ErrorType{})
return true
}
params := append([]types.Type{member.Subject.GetType()}, requirement.Parameters...)
n.Symbol = symbols.NewFunction(requirement.Name, &symbols.FunctionSignature{Parameters: params, RequiredParameters: len(params), ReturnType: requirement.ReturnType})
n.Args = append([]parser.ExpressionNode{member.Subject}, n.Args...)
n.Method = true
n.TraitCall = true
n.TraitSlot = slot
return true
}
return false
}
module, owner, receiverIsPointer, ok := methodOwnerIdentity(member.Subject.GetType())
var method *symbols.Symbol
if ok {
method = a.analyser.methods[module+":"+owner][member.Field.Name]
}
adjustment := structuralReceiverDirect
if method != nil && method.MethodOwnerType != nil {
_, patternAdjustment, matches := structuralReceiverMatch(method, member.Subject.GetType())
if !matches {
method = nil
} else {
adjustment = patternAdjustment
}
}
if method == nil {
var ambiguous bool
method, adjustment, ambiguous = a.analyser.findStructuralMethod(member.Field.Name, member.Subject.GetType())
if ambiguous {
a.errorf(n, "method %q is ambiguous for receiver type %v", member.Field.Name, member.Subject.GetType())
n.SetType(types.ErrorType{})
return true
}
if method == nil {
return false
}
}
if method.StaticMethod {
return false
}
if method.DefinitionModule != a.analyser.currentMod && !method.Public {
a.errorf(n, "method %q is not public", member.Field.Name)
n.SetType(types.ErrorType{})
return true
}
if method.Template {
arguments, remaining, explicit, valid := a.analyser.explicitCallTypeArguments(method.GenericParameters, method.Signature, n.Args, true, n)
if !valid {
n.SetType(types.ErrorType{})
return true
}
if explicit {
n.Args = remaining
method = dependentGenericFunctionSymbol(method, arguments)
}
}
if method.Template && len(member.Field.TypeArguments) != 0 {
arguments := a.analyser.resolveGenericArguments(member.Field.TypeArguments)
if a.templatesOnly || hasTypeParameters(arguments) {
if !a.analyser.checkGenericArguments(n, method.GenericParameters, arguments) {
n.SetType(types.ErrorType{})
return true
}
method = dependentGenericFunctionSymbol(method, arguments)
} else {
symbol := a.analyser.specializeGenericFunctionSymbol(method, arguments, n)
if symbol == nil {
n.SetType(types.ErrorType{})
return true
}
method = symbol
}
}
receiver := member.Subject
if method.MethodOwnerType != nil {
switch adjustment {
case structuralReceiverReference:
receiver = &parser.UnaryOpNode{Op: parser.UnaryOpReference, Operand: receiver, Loc: receiver.GetLoc()}
case structuralReceiverMutableReference:
receiver = &parser.UnaryOpNode{Op: parser.UnaryOpMutableReference, Operand: receiver, Loc: receiver.GetLoc()}
case structuralReceiverDereference:
receiver = &parser.UnaryOpNode{Op: parser.UnaryOpDereference, Operand: receiver, Loc: receiver.GetLoc()}
}
} else {
expected := method.Signature.Parameters[0]
expectsPointer := method.MethodReceiver != types.TraitReceiverValue
if expectsPointer && !receiverIsPointer {
op := parser.UnaryOpReference
if ptr := types.Underlying(expected).(types.PointerType); ptr.Mutable {
op = parser.UnaryOpMutableReference
}
receiver = &parser.UnaryOpNode{Op: op, Operand: receiver, Loc: receiver.GetLoc()}
} else if !expectsPointer && receiverIsPointer {
receiver = &parser.UnaryOpNode{Op: parser.UnaryOpDereference, Operand: receiver, Loc: receiver.GetLoc()}
}
}
n.Args = append([]parser.ExpressionNode{receiver}, n.Args...)
n.Symbol = method
n.Method = true
if method.DefinitionModule != a.analyser.currentMod {
n.Name = &parser.IdentifierNode{Name: method.Name, Module: method.DefinitionModule, ResolvedModuleName: method.DefinitionModule, Loc: member.Loc, Symbol: method}
}
return true
}
type structuralReceiverAdjustment uint8
const (
structuralReceiverDirect structuralReceiverAdjustment = iota
structuralReceiverReference
structuralReceiverMutableReference
structuralReceiverDereference
)
func (a *Analyser) findStructuralMethod(name string, actual types.Type) (*symbols.Symbol, structuralReceiverAdjustment, bool) {
var selected *symbols.Symbol
selectedAdjustment := structuralReceiverDirect
selectedRank := 100
ambiguous := false
for _, candidate := range a.structuralMethods[name] {
if candidate.StaticMethod || len(candidate.Signature.Parameters) == 0 {
continue
}
rank, adjustment, matches := structuralReceiverMatch(candidate, actual)
if !matches || rank > selectedRank {
continue
}
if rank == selectedRank {
ambiguous = true
continue
}
selected = candidate
selectedAdjustment = adjustment
selectedRank = rank
ambiguous = false
}
return selected, selectedAdjustment, ambiguous
}
func structuralReceiverMatch(method *symbols.Symbol, actual types.Type) (int, structuralReceiverAdjustment, bool) {
expected := method.Signature.Parameters[0]
if typePatternMatches(expected, actual, false) {
return 0, structuralReceiverDirect, true
}
if typePatternMatches(expected, actual, true) {
return 1, structuralReceiverDirect, true
}
owner := method.MethodOwnerType
if method.MethodReceiver != types.TraitReceiverValue {
if typePatternMatches(owner, actual, false) {
if method.MethodReceiver == types.TraitReceiverMutablePointer {
return 2, structuralReceiverMutableReference, true
}
return 2, structuralReceiverReference, true
}
if typePatternMatches(owner, actual, true) {
if method.MethodReceiver == types.TraitReceiverMutablePointer {
return 3, structuralReceiverMutableReference, true
}
return 3, structuralReceiverReference, true
}
}
if method.MethodReceiver == types.TraitReceiverValue {
if pointer, ok := actual.(types.PointerType); ok && typePatternMatches(owner, pointer.Base, false) {
return 2, structuralReceiverDereference, true
}
}
return 0, structuralReceiverDirect, false
}
func typePatternMatches(pattern, actual types.Type, allowCapabilityCoercion bool) bool {
if _, ok := pattern.(types.TypeParameter); ok {
return true
}
switch pattern := pattern.(type) {
case types.DefinedType:
actual, ok := actual.(types.DefinedType)
if !ok || pattern.Module != actual.Module || len(pattern.TypeArguments) != len(actual.TypeArguments) {
return false
}
if pattern.GenericName != "" || actual.GenericName != "" {
if pattern.GenericName == "" || pattern.GenericName != actual.GenericName {
return false
}
} else if pattern.Name != actual.Name {
return false
}
for i := range pattern.TypeArguments {
if !typePatternMatches(pattern.TypeArguments[i], actual.TypeArguments[i], allowCapabilityCoercion) {
return false
}
}
return true
case types.PointerType:
actual, ok := actual.(types.PointerType)
if !ok || (pattern.Mutable != actual.Mutable && !(allowCapabilityCoercion && !pattern.Mutable && actual.Mutable)) {
return false
}
return typePatternMatches(pattern.Base, actual.Base, allowCapabilityCoercion)
case types.SliceType:
actual, ok := actual.(types.SliceType)
if !ok || (pattern.Mutable != actual.Mutable && !(allowCapabilityCoercion && !pattern.Mutable && actual.Mutable)) {
return false
}
return typePatternMatches(pattern.Base, actual.Base, allowCapabilityCoercion)
case types.ArrayType:
actual, ok := actual.(types.ArrayType)
return ok && pattern.Length == actual.Length && typePatternMatches(pattern.Base, actual.Base, allowCapabilityCoercion)
case types.FunctionType:
actual, ok := actual.(types.FunctionType)
if !ok || len(pattern.Parameters) != len(actual.Parameters) || pattern.TypedVariadic != actual.TypedVariadic {
return false
}
for i := range pattern.Parameters {
if !typePatternMatches(pattern.Parameters[i], actual.Parameters[i], allowCapabilityCoercion) {
return false
}
}
return typePatternMatches(pattern.ReturnType, actual.ReturnType, allowCapabilityCoercion)
default:
return pattern.Equals(actual)
}
}
func (a *Attributor) attributeStaticTraitMethodCall(
call *parser.FunctionCallNode,
member *parser.FieldAccessNode,
view *types.StaticTraitView,
) bool {
var requirement *types.TraitMethod
requirementSlot := -1
for i := range view.Trait.Methods {
if view.Trait.Methods[i].Name == member.Field.Name {
requirement = &view.Trait.Methods[i]
requirementSlot = i
break
}
}
if requirement == nil {
a.errorf(call, "trait %v has no method %q", view.Trait, member.Field.Name)
call.SetType(types.ErrorType{})
return true
}
if requirement.Receiver == types.TraitReceiverPointer && view.Access == types.TraitReceiverValue {
a.errorf(call, "method %q requires *%v access", requirement.Name, view.Trait)
call.SetType(types.ErrorType{})
return true
}
if requirement.Receiver == types.TraitReceiverMutablePointer && view.Access != types.TraitReceiverMutablePointer {
a.errorf(call, "method %q requires *mut %v access", requirement.Name, view.Trait)
call.SetType(types.ErrorType{})
return true
}
subjectType := member.Subject.GetType()
if parameter, symbolic := genericTypeParameterBase(subjectType); symbolic {
parameters := make([]types.Type, len(requirement.Parameters)+1)
parameters[0] = subjectType
for i, required := range requirement.Parameters {
parameters[i+1] = types.SubstituteSelf(required, parameter)
}
method := symbols.NewFunction(requirement.Name, &symbols.FunctionSignature{
Parameters: parameters,
RequiredParameters: len(parameters),
ReturnType: types.SubstituteSelf(requirement.ReturnType, parameter),
})
method.Method = true
method.MethodReceiver = requirement.Receiver
method.GenericParameters = append([]types.TypeParameter(nil), requirement.GenericParameters...)
method.Template = len(method.GenericParameters) != 0
method.TraitRequirement = true
method.RequirementTrait = view.Trait
method.RequirementSlot = requirementSlot
method.RequirementAccess = view.Access
call.Args = append([]parser.ExpressionNode{member.Subject}, call.Args...)
call.Symbol = method
call.Method = true
if method.Template && len(member.Field.TypeArguments) != 0 {
arguments := a.analyser.resolveGenericArguments(member.Field.TypeArguments)
if !a.analyser.checkGenericArguments(call, method.GenericParameters, arguments) {
call.SetType(types.ErrorType{})
return true
}
call.Symbol = dependentGenericFunctionSymbol(method, arguments)
}
return true
}
_, _, subjectIsPointer, _ := methodOwnerIdentity(subjectType)
receiverIsPointer := view.Access != types.TraitReceiverValue || subjectIsPointer
var probe types.PointerType
if receiverIsPointer {
probe, _ = types.Underlying(subjectType).(types.PointerType)
} else {
probe = types.PointerType{Base: subjectType, Mutable: view.Access == types.TraitReceiverMutablePointer}
}
target := types.TraitPointerType{
Trait: view.Trait,
Mutable: view.Access == types.TraitReceiverMutablePointer,
}
methods, conforms := a.analyser.structuralConformance(probe, target, call)
if !conforms {
a.errorf(call, "type %v does not implement %v", subjectType, view.Trait)
call.SetType(types.ErrorType{})
return true
}
method := methods[requirementSlot]
if method.Template && len(member.Field.TypeArguments) != 0 {
arguments := a.analyser.resolveGenericArguments(member.Field.TypeArguments)
if a.templatesOnly || hasTypeParameters(arguments) {
if !a.analyser.checkGenericArguments(call, method.GenericParameters, arguments) {
call.SetType(types.ErrorType{})
return true
}
method = dependentGenericFunctionSymbol(method, arguments)
} else {
symbol := a.analyser.specializeGenericFunctionSymbol(method, arguments, call)
if symbol == nil {
call.SetType(types.ErrorType{})
return true
}
method = symbol
}
}
receiver := member.Subject
expected := method.Signature.Parameters[0]
expectsPointer := method.MethodReceiver != types.TraitReceiverValue
if expectsPointer && !receiverIsPointer {
op := parser.UnaryOpReference
if ptr := types.Underlying(expected).(types.PointerType); ptr.Mutable {
op = parser.UnaryOpMutableReference
}
receiver = &parser.UnaryOpNode{Op: op, Operand: receiver, Loc: receiver.GetLoc()}
} else if !expectsPointer && receiverIsPointer {
receiver = &parser.UnaryOpNode{Op: parser.UnaryOpDereference, Operand: receiver, Loc: receiver.GetLoc()}
}
call.Args = append([]parser.ExpressionNode{receiver}, call.Args...)
call.Symbol = method
call.Method = true
if method.DefinitionModule != a.analyser.currentMod {
call.Name = &parser.IdentifierNode{
Name: method.Name, Module: method.DefinitionModule, ResolvedModuleName: method.DefinitionModule,
Loc: member.Loc, Symbol: method,
}
}
return true
}
func methodOwnerIdentity(t types.Type) (module, name string, pointer bool, ok bool) {
if defined, isDefined := t.(types.DefinedType); isDefined && defined.Module == "" && defined.Name == "str" {
return "builtin", "str", false, true
}
if ptr, isPointer := types.Underlying(t).(types.PointerType); isPointer && !ptr.Mutable && ptr.Base.Equals(types.PrimitiveU8) {
return "builtin", "cstr", false, true
}
if ptr, isPointer := types.Underlying(t).(types.PointerType); isPointer {
t = ptr.Base
pointer = true
}
if defined, isDefined := t.(types.DefinedType); isDefined && defined.Module == "" && defined.Name == "str" {
return "builtin", "str", pointer, true
}
switch t := t.(type) {
case types.DefinedType:
if t.GenericName != "" {
return t.Module, t.GenericName, pointer, true
}
return t.Module, t.Name, pointer, true
case *types.AliasRef:
return t.Module, t.Name, pointer, true
case types.PrimitiveType:
return "builtin", t.String(), pointer, true
default:
return "", "", pointer, false
}
}
func isUnresolvedEnum(t types.Type) bool {
_, ok := t.(types.UnresolvedEnum)
return ok
}
func isEnumOrFlags(t types.Type) bool {
switch types.Underlying(t).(type) {
case types.EnumType, types.FlagsType:
return true
}
return false
}
func (a *Attributor) resolveEnumLiteral(n *parser.EnumLiteralNode, expected types.Type) {
var value string
var ok bool
switch t := types.Underlying(expected).(type) {
case types.EnumType:
value, ok = t.VariantValue(n.Variant)
case types.FlagsType:
value, ok = t.VariantValue(n.Variant)
}
if !ok {
a.errorf(n, "%s has no member %q", expected, n.Variant)
n.SetType(types.ErrorType{})
return
}
n.Value = value
n.SetType(expected)
}
type returnTypeCandidate struct {
node parser.Node
ty types.Type
}
func (a *Attributor) mergeReturnTypes(candidates []returnTypeCandidate, diagnostic string) types.Type {
if len(candidates) == 0 {
return types.PrimitiveVoid
}
for _, candidate := range candidates {
if types.HasError(candidate.ty) {
return types.ErrorType{}
}
}
current := candidates[0].ty
for _, candidate := range candidates[1:] {
if types.IsNumeric(current) && types.IsNumeric(candidate.ty) {
current = types.PromoteNumeric(current, candidate.ty)
if types.HasError(current) {
if diagnostic != "" {
a.errorf(candidate.node, "%s", diagnostic)
}
return current
}
continue
}
if !current.Equals(candidate.ty) {
if diagnostic != "" {
a.errorf(candidate.node, "%s: expected %v, got %v", diagnostic, current, candidate.ty)
}
return types.ErrorType{}
}
}
return current
}
func (a *Attributor) attributeBlock(n *parser.BlockNode) {
for _, child := range n.Body {
a.attributeNode(child)
}
if !n.Expression {
n.SetType(types.PrimitiveVoid)
return
}
if result, ok := parser.BlockResult(n); ok {
n.SetType(result.GetType())
return
}
if !parser.NodeFallsThrough(n) {
n.SetType(types.PrimitiveVoid)
return
}
n.SetType(types.PrimitiveVoid)
}
func (a *Attributor) attributeIf(n *parser.IfNode) {
a.attributeExpr(n.IfBranch.Condition)
a.attributeNode(n.IfBranch.Node)
for _, branch := range n.ElseIfBranches {
a.attributeExpr(branch.Condition)
a.attributeNode(branch.Node)
}
if n.ElseBranch != nil {
a.attributeNode(n.ElseBranch)
}
if !n.Expression {
n.SetType(types.PrimitiveVoid)
return
}
if types.HasError(n.IfBranch.Condition.GetType()) {
n.SetType(types.ErrorType{})
return
}
for _, branch := range n.ElseIfBranches {
if types.HasError(branch.Condition.GetType()) {
n.SetType(types.ErrorType{})
return
}
}
var candidates []returnTypeCandidate
addBranch := func(block *parser.BlockNode) {
if block != nil && parser.NodeFallsThrough(block) {
candidates = append(candidates, returnTypeCandidate{node: block, ty: block.GetType()})
}
}
addBranch(n.IfBranch.Node)
for _, branch := range n.ElseIfBranches {
addBranch(branch.Node)
}
addBranch(n.ElseBranch)
if len(candidates) == 0 {
n.SetType(types.PrimitiveVoid)
return
}
// A surrounding declaration, assignment, return, or call may provide a
// common type that each branch can convert to. Defer mismatch reporting to
// validation, once that context is available.
n.SetType(a.mergeReturnTypes(candidates, ""))
}
func (a *Attributor) attributeMatch(n *parser.MatchNode) {
for _, subject := range n.Subjects {
a.attributeExpr(subject)
}
if n.Binding != nil {
n.Binding.Type = n.Subjects[0].GetType()
}
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
}
a.attributeMatchPattern(pattern, n.Subjects[subjectIndex].GetType())
}
if arm.Guard != nil {
a.attributeExpr(arm.Guard)
}
a.attributeExpr(arm.Body)
}
if !n.Expression {
n.SetType(types.PrimitiveVoid)
return
}
for _, subject := range n.Subjects {
if types.HasError(subject.GetType()) {
n.SetType(types.ErrorType{})
return
}
}
for i := range n.Arms {
arm := &n.Arms[i]
if anyErrorExpression(arm.Guard, arm.Body) {
n.SetType(types.ErrorType{})
return
}
}
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()})
}
}
if len(candidates) == 0 {
n.SetType(types.PrimitiveVoid)
return
}
n.SetType(a.mergeReturnTypes(candidates, ""))
}
func (a *Attributor) attributeMatchPattern(pattern *parser.MatchPatternNode, subjectType types.Type) {
if pattern == nil {
return
}
if types.HasError(subjectType) {
return
}
switch pattern.Kind {
case parser.MatchPatternLiteral:
a.attributeExpr(pattern.Literal)
case parser.MatchPatternRange:
a.attributeExpr(pattern.Start)
a.attributeExpr(pattern.End)
case parser.MatchPatternAlternative:
for _, alternative := range pattern.Alternatives {
a.attributeMatchPattern(alternative, subjectType)
}
case parser.MatchPatternVariant:
if info, tagged := types.TaggedUnion(subjectType); tagged {
variant, _, ok := info.Variant(pattern.Variant)
if !ok {
a.errorf(pattern, "tagged union %v has no variant %q", subjectType, pattern.Variant)
return
}
pattern.TagValue = variant.TagValue
pattern.PayloadType = types.StructType{Fields: variant.Fields}
if len(variant.Fields) == 0 && pattern.Payload {
a.errorf(pattern, "empty variant .%s pattern does not take parentheses", variant.Name)
}
if len(pattern.Bindings) != len(variant.Fields) {
a.errorf(pattern, "variant %q pattern expects %d payload bindings, but %d provided", variant.Name, len(variant.Fields), len(pattern.Bindings))
return
}
seenFields := map[string]bool{}
for i := range pattern.Bindings {
binding := &pattern.Bindings[i]
fieldIndex := i
if binding.Field != "" {
fieldIndex = -1
for j, field := range variant.Fields {
if field.L == binding.Field {
fieldIndex = j
break
}
}
if fieldIndex < 0 {
a.errorf(pattern, "variant %q has no payload field %q", variant.Name, binding.Field)
continue
}
if seenFields[binding.Field] {
a.errorf(pattern, "payload field %q appears more than once in pattern", binding.Field)
}
seenFields[binding.Field] = true
}
binding.Field = variant.Fields[fieldIndex].L
if binding.Symbol != nil {
binding.Symbol.Type = variant.Fields[fieldIndex].R
}
}
return
}
enumType, ok := types.Underlying(subjectType).(types.EnumType)
if !ok {
a.errorf(pattern, "variant pattern .%s requires an enum or tagged union subject", pattern.Variant)
return
}
value, ok := enumType.VariantValue(pattern.Variant)
if !ok {
a.errorf(pattern, "enum %v has no member %q", subjectType, pattern.Variant)
return
}
if pattern.Payload {
a.errorf(pattern, "enum member .%s has no payload", pattern.Variant)
}
pattern.TagValue = value
}
}
func collectFunctionReturnNodes(body []parser.Node) []*parser.ControlKeywordNode {
var returnNodes []*parser.ControlKeywordNode
for _, node := range body {
returnNodes = append(returnNodes, collectFunctionReturnNodesFromNode(node)...)
}
return returnNodes
}
func collectFunctionReturnNodesFromNode(node parser.Node) []*parser.ControlKeywordNode {
if node == nil {
return nil
}
switch n := node.(type) {
case *parser.ControlKeywordNode:
if n.Keyword == tokeniser.KeywordReturn {
return []*parser.ControlKeywordNode{n}
}
case *parser.BlockNode:
return collectFunctionReturnNodes(n.Body)
case *parser.IfNode:
result := collectFunctionReturnNodes(n.IfBranch.Node.Body)
for _, branch := range n.ElseIfBranches {
result = append(result, collectFunctionReturnNodes(branch.Node.Body)...)
}
if n.ElseBranch != nil {
result = append(result, collectFunctionReturnNodes(n.ElseBranch.Body)...)
}
return result
case *parser.MatchNode:
var result []*parser.ControlKeywordNode
for _, arm := range n.Arms {
result = append(result, collectFunctionReturnNodesFromExpr(arm.Body)...)
}
return result
case *parser.ForNode:
return collectFunctionReturnNodes(n.Body.Body)
case *parser.RangeForNode:
return collectFunctionReturnNodes(n.Body.Body)
case *parser.ForEachNode:
return collectFunctionReturnNodes(n.Body.Body)
case *parser.DeclarationNode:
return collectFunctionReturnNodesFromExpr(n.Value)
case *parser.MultiDeclarationNode:
return collectFunctionReturnNodesFromExpr(n.Value)
case *parser.AssignmentNode:
return collectFunctionReturnNodesFromExpr(n.Value)
case parser.ExpressionNode:
return collectFunctionReturnNodesFromExpr(n)
}
return nil
}
func collectFunctionReturnNodesFromExpr(expr parser.ExpressionNode) []*parser.ControlKeywordNode {
if expr == nil {
return nil
}
collect := func(expressions ...parser.ExpressionNode) []*parser.ControlKeywordNode {
var result []*parser.ControlKeywordNode
for _, expression := range expressions {
result = append(result, collectFunctionReturnNodesFromExpr(expression)...)
}
return result
}
switch n := expr.(type) {
case *parser.BlockNode, *parser.IfNode, *parser.MatchNode:
return collectFunctionReturnNodesFromNode(n)
case *parser.BinaryOpNode:
return collect(n.Operand1, n.Operand2)
case *parser.UnaryOpNode:
return collect(n.Operand)
case *parser.FunctionCallNode:
return collect(append([]parser.ExpressionNode{n.Callee}, n.Args...)...)
case *parser.InlineAsmNode:
inputs := make([]parser.ExpressionNode, len(n.Inputs))
for i := range n.Inputs {
inputs[i] = n.Inputs[i].Value
}
return collect(inputs...)
case *parser.IndexExprNode:
return collect(n.Subject, n.Index)
case *parser.SliceExprNode:
return collect(n.Subject, n.Start, n.End)
case *parser.FieldAccessNode:
return collect(n.Subject)
case *parser.CastNode:
return collect(n.Operand)
case *parser.SizeOfExprNode:
return collect(n.Operand)
case *parser.AlignOfNode:
return collect(n.Expression)
case *parser.StructLiteralNode:
var expressions []parser.ExpressionNode
for _, field := range n.Fields {
expressions = append(expressions, field.R)
}
return collect(expressions...)
case *parser.SliceLiteralNode:
return collect(append(append([]parser.ExpressionNode(nil), n.Elements...), n.RepeatValue, n.RepeatAmount)...)
}
return nil
}