package lang import "fmt" type ExprMeaning int const ( UnresolvedExpr ExprMeaning = iota LiteralExpr LocalReferenceExpr FunctionReferenceExpr ClassReferenceExpr EnumReferenceExpr ImportReferenceExpr GotlinCallExpr GoCallExpr ClassConstructionExpr EnumConstructionExpr FieldAccessExpr MethodCallExpr PropagateResultExpr MatchValueExpr SQLExpression MappingExpression CoroutineExpression ) type ExprMeta struct{ Semantic *HIRExpr } type HIRExpr struct { ID int Type Type Meaning ExprMeaning Symbol *Symbol Node HIRNode } type HIRNode interface{ hirNode() } type HIRLiteral struct{} func (HIRLiteral) hirNode() {} type HIRReference struct{ Target *Symbol } func (HIRReference) hirNode() {} type HIRGoCall struct { Callee *HIRExpr Result Type Params []Type Variadic bool InjectContext bool } func (HIRGoCall) hirNode() {} type HIRGotlinCall struct { Target *Symbol Result Type } func (HIRGotlinCall) hirNode() {} type HIRClassConstruction struct{ Class *ClassSymbol } func (HIRClassConstruction) hirNode() {} type HIREnumConstruction struct { EnumName, VariantName string } func (HIREnumConstruction) hirNode() {} type HIRPropagateResult struct { Value *HIRExpr Type Type } func (HIRPropagateResult) hirNode() {} type HIRMatch struct { Value *HIRExpr Cases []HIRMatchCase Type Type } func (HIRMatch) hirNode() {} type HIRMatchCase struct { EnumName, VariantName string Bindings []string Value *HIRExpr } type HIRCoroutine struct{ Operation string } func (HIRCoroutine) hirNode() {} type HIRSQL struct{ Type Type } func (HIRSQL) hirNode() {} type HIRMapping struct { Source Type Target Type } func (HIRMapping) hirNode() {} type HIRFunction struct { Symbol *Symbol Decl *FunctionDecl Scope *Scope } type HIRProgram struct { Functions []*HIRFunction Methods []*HIRFunction Expressions []*HIRExpr } func exprMeta(expr Expr) *HIRExpr { switch value := expr.(type) { case IdentExpr: return value.Meta.Semantic case IntExpr: return value.Meta.Semantic case FloatExpr: return value.Meta.Semantic case StringExpr: return value.Meta.Semantic case BoolExpr: return value.Meta.Semantic case NullExpr: return value.Meta.Semantic case UnaryExpr: return value.Meta.Semantic case BinaryExpr: return value.Meta.Semantic case CallExpr: return value.Meta.Semantic case SelectorExpr: return value.Meta.Semantic case SafeSelectorExpr: return value.Meta.Semantic case NonNullExpr: return value.Meta.Semantic case TryExpr: return value.Meta.Semantic case IndexExpr: return value.Meta.Semantic case EnumVariantExpr: return value.Meta.Semantic case MatchExpr: return value.Meta.Semantic case LambdaExpr: return value.Meta.Semantic default: return nil } } func ResolvedType(expr Expr) Type { if semantic := exprMeta(expr); semantic != nil { return semantic.Type } return UnknownType{} } func ResolvedMeaning(expr Expr) ExprMeaning { if semantic := exprMeta(expr); semantic != nil { return semantic.Meaning } return UnresolvedExpr } func withExprMeta(expr Expr, semantic *HIRExpr) Expr { switch value := expr.(type) { case IdentExpr: value.Meta.Semantic = semantic return value case IntExpr: value.Meta.Semantic = semantic return value case FloatExpr: value.Meta.Semantic = semantic return value case StringExpr: value.Meta.Semantic = semantic return value case BoolExpr: value.Meta.Semantic = semantic return value case NullExpr: value.Meta.Semantic = semantic return value case UnaryExpr: value.Meta.Semantic = semantic return value case BinaryExpr: value.Meta.Semantic = semantic return value case CallExpr: value.Meta.Semantic = semantic return value case SelectorExpr: value.Meta.Semantic = semantic return value case SafeSelectorExpr: value.Meta.Semantic = semantic return value case NonNullExpr: value.Meta.Semantic = semantic return value case TryExpr: value.Meta.Semantic = semantic return value case IndexExpr: value.Meta.Semantic = semantic return value case EnumVariantExpr: value.Meta.Semantic = semantic return value case MatchExpr: value.Meta.Semantic = semantic return value case LambdaExpr: value.Meta.Semantic = semantic return value default: return expr } } type semanticResolver struct { program *SemanticProgram err error } func (resolver *semanticResolver) resolve() error { resolver.program.HIR = &HIRProgram{} for index := range resolver.program.Syntax.Functions { decl := &resolver.program.Syntax.Functions[index] symbol, _ := resolver.program.Global.Lookup(decl.Name) scope := NewScope(resolver.program.Global) for _, param := range decl.Params { typ, _ := resolver.program.ResolveTypeRefWithParams(param.TypeRef, decl.TypeParams) _ = scope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ}) } result, _ := resolver.program.ResolveTypeRefWithParams(decl.ReturnRef, decl.TypeParams) resolver.resolveStmts(decl.Body, scope, nil, result) resolver.program.HIR.Functions = append(resolver.program.HIR.Functions, &HIRFunction{Symbol: symbol, Decl: decl, Scope: scope}) } for classIndex := range resolver.program.Syntax.Classes { decl := &resolver.program.Syntax.Classes[classIndex] class := resolver.program.ClassInfo[decl.Name] for methodIndex := range decl.Methods { method := &decl.Methods[methodIndex] scope := NewScope(resolver.program.Global) _ = scope.Define(&Symbol{Name: "this", Kind: VariableSymbol, Type: ClassType{Class: class}}) for _, param := range method.Params { params := append(append([]string{}, decl.TypeParams...), method.TypeParams...) typ, _ := resolver.program.ResolveTypeRefWithParams(param.TypeRef, params) _ = scope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ}) } params := append(append([]string{}, decl.TypeParams...), method.TypeParams...) result, _ := resolver.program.ResolveTypeRefWithParams(method.ReturnRef, params) resolver.resolveStmts(method.Body, scope, class, result) resolver.program.HIR.Methods = append(resolver.program.HIR.Methods, &HIRFunction{Symbol: class.Methods[method.Name], Decl: method, Scope: scope}) } } return resolver.err } func (resolver *semanticResolver) resolveStmts(stmts []Stmt, scope *Scope, class *ClassSymbol, returnType Type) { for index, stmt := range stmts { switch value := stmt.(type) { case VarDecl: expected := Type(UnknownType{}) if value.Type != "" { expected, _ = resolver.program.ResolveType(value.Type) } value.Value, _ = resolver.resolveExpr(value.Value, scope, class, expected) if isUnknownType(expected) { expected = exprMeta(value.Value).Type } _ = scope.Define(&Symbol{Name: value.Name, Kind: VariableSymbol, Type: expected, Mutable: value.Mutable, Decl: &value}) stmts[index] = value case MultiVarDecl: value.Value, _ = resolver.resolveExpr(value.Value, scope, class, UnknownType{}) valueTypes := make([]Type, len(value.Names)) for index := range valueTypes { valueTypes[index] = UnknownType{} } if result, ok := ResolvedType(value.Value).(GenericType); ok && result.Base.String() == "Result" && len(result.Args) == 2 && len(valueTypes) == 2 { valueTypes[0] = result.Args[0] valueTypes[1] = NullableType{Element: result.Args[1]} } else if tuple, ok := ResolvedType(value.Value).(TupleType); ok && len(tuple.Elements) == len(valueTypes) { copy(valueTypes, tuple.Elements) } for index, name := range value.Names { _ = scope.Define(&Symbol{Name: name, Kind: VariableSymbol, Type: valueTypes[index], Mutable: value.Mutable}) } stmts[index] = value case AssignStmt: expected := Type(UnknownType{}) if symbol, ok := scope.Lookup(value.Name); ok { expected = symbol.Type } else if class != nil && class.Fields[value.Name] != nil { expected = class.Fields[value.Name].Type } else { resolver.addDiagnostic(SemanticDiagnostic{Code: "undefined-variable", Message: "undefined variable " + value.Name, Severity: DiagnosticError, Span: SourceSpan{Start: value.Pos, End: value.Pos + len(value.Name)}}) } value.Value, _ = resolver.resolveExpr(value.Value, scope, class, expected) stmts[index] = value case AddAssignStmt: _, local := scope.Lookup(value.Name) field := false if class != nil { _, field = class.Fields[value.Name] } if !local && !field { resolver.addDiagnostic(SemanticDiagnostic{Code: "undefined-variable", Message: "undefined variable " + value.Name, Severity: DiagnosticError, Span: SourceSpan{Start: value.Pos, End: value.Pos + len(value.Name)}}) } value.Value, _ = resolver.resolveExpr(value.Value, scope, class, UnknownType{}) stmts[index] = value case MultiAssignStmt: for index, name := range value.Names { if _, ok := scope.Lookup(name); !ok { position := 0 if index < len(value.Positions) { position = value.Positions[index] } resolver.addDiagnostic(SemanticDiagnostic{Code: "undefined-variable", Message: "undefined variable " + name, Severity: DiagnosticError, Span: SourceSpan{Start: position, End: position + len(name)}}) } } value.Value, _ = resolver.resolveExpr(value.Value, scope, class, UnknownType{}) stmts[index] = value case ReturnStmt: if value.Value != nil { value.Value, _ = resolver.resolveExpr(value.Value, scope, class, returnType) stmts[index] = value } case ThrowStmt: value.Value, _ = resolver.resolveExpr(value.Value, scope, class, NamedType{Name: "Error"}) stmts[index] = value case DeferStmt: value.Value, _ = resolver.resolveExpr(value.Value, scope, class, NamedType{Name: "Unit"}) stmts[index] = value case ExprStmt: value.Value, _ = resolver.resolveExpr(value.Value, scope, class, UnknownType{}) stmts[index] = value case IfStmt: value.Cond, _ = resolver.resolveExpr(value.Cond, scope, class, NamedType{Name: "Boolean"}) resolver.resolveStmts(value.Then, NewScope(scope), class, returnType) resolver.resolveStmts(value.Else, NewScope(scope), class, returnType) stmts[index] = value case WhileStmt: value.Cond, _ = resolver.resolveExpr(value.Cond, scope, class, NamedType{Name: "Boolean"}) resolver.resolveStmts(value.Body, NewScope(scope), class, returnType) stmts[index] = value case ForEachStmt: value.Source, _ = resolver.resolveExpr(value.Source, scope, class, UnknownType{}) bodyScope := NewScope(scope) element := collectionElement(exprMeta(value.Source).Type) _ = bodyScope.Define(&Symbol{Name: value.Name, Kind: VariableSymbol, Type: element}) resolver.resolveStmts(value.Body, bodyScope, class, returnType) stmts[index] = value case MatchStmt: value.Value, _ = resolver.resolveExpr(value.Value, scope, class, UnknownType{}) for caseIndex := range value.Cases { matchCase := &value.Cases[caseIndex] caseScope := NewScope(scope) resolver.defineMatchBindings(caseScope, matchCase.EnumName, matchCase.VariantName, matchCase.Bindings) resolver.resolveStmts(matchCase.Body, caseScope, class, returnType) } resolver.validateStatementMatch(value) stmts[index] = value case TryCatchStmt: resolver.resolveStmts(value.TryBody, NewScope(scope), class, returnType) catchScope := NewScope(scope) catchType, _ := resolver.program.ResolveType(value.CatchType) _ = catchScope.Define(&Symbol{Name: value.CatchName, Kind: VariableSymbol, Type: catchType}) resolver.resolveStmts(value.CatchBody, catchScope, class, returnType) } } } func (resolver *semanticResolver) resolveExpr(expr Expr, scope *Scope, class *ClassSymbol, expected Type) (Expr, Type) { environment := TypeEnvironment{Scope: scope, Class: class} switch value := expr.(type) { case UnaryExpr: value.Value, _ = resolver.resolveExpr(value.Value, scope, class, UnknownType{}) expr = value case BinaryExpr: value.Left, _ = resolver.resolveExpr(value.Left, scope, class, UnknownType{}) value.Right, _ = resolver.resolveExpr(value.Right, scope, class, UnknownType{}) expr = value case CallExpr: value.Callee, _ = resolver.resolveExpr(value.Callee, scope, class, UnknownType{}) for index := range value.Args { argumentExpected := Type(UnknownType{}) if selector, ok := value.Callee.(SelectorExpr); ok { if receiver, ok := selector.Receiver.(IdentExpr); ok && receiver.Name == "Result" { if selector.Name == "Err" { argumentExpected = NamedType{Name: "Error"} } else if selector.Name == "Ok" { if result, ok := expected.(GenericType); ok && result.Base.String() == "Result" && len(result.Args) == 2 { argumentExpected = result.Args[0] } } } } value.Args[index], _ = resolver.resolveExpr(value.Args[index], scope, class, argumentExpected) } for index := range value.NamedArgs { value.NamedArgs[index].Value, _ = resolver.resolveExpr(value.NamedArgs[index].Value, scope, class, UnknownType{}) } expr = value case SelectorExpr: value.Receiver, _ = resolver.resolveExpr(value.Receiver, scope, class, UnknownType{}) expr = value case SafeSelectorExpr: value.Receiver, _ = resolver.resolveExpr(value.Receiver, scope, class, UnknownType{}) expr = value case NonNullExpr: value.Value, _ = resolver.resolveExpr(value.Value, scope, class, UnknownType{}) expr = value case TryExpr: value.Value, _ = resolver.resolveExpr(value.Value, scope, class, UnknownType{}) expr = value case IndexExpr: value.Receiver, _ = resolver.resolveExpr(value.Receiver, scope, class, UnknownType{}) value.Index, _ = resolver.resolveExpr(value.Index, scope, class, NamedType{Name: "Int"}) expr = value case EnumVariantExpr: for index := range value.Values { value.Values[index], _ = resolver.resolveExpr(value.Values[index], scope, class, UnknownType{}) } expr = value case MatchExpr: value.Value, _ = resolver.resolveExpr(value.Value, scope, class, UnknownType{}) for index := range value.Cases { matchCase := &value.Cases[index] caseScope := NewScope(scope) resolver.defineMatchBindings(caseScope, matchCase.EnumName, matchCase.VariantName, matchCase.Bindings) matchCase.Value, _ = resolver.resolveExpr(matchCase.Value, caseScope, class, expected) } resolver.validateValueMatch(value) expr = value case LambdaExpr: lambdaScope := NewScope(scope) if value.ImplicitIt { _ = lambdaScope.Define(&Symbol{Name: "it", Kind: VariableSymbol, Type: UnknownType{}}) } for _, param := range value.Params { typ, _ := resolver.program.ResolveTypeRef(param.TypeRef) _ = lambdaScope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ}) } resolver.resolveStmts(value.Body, lambdaScope, class, functionResult(expected)) expr = value } typ := resolver.program.TypeOf(expr, environment) if isUnknownType(typ) && !isUnknownType(expected) { typ = expected } if binary, ok := expr.(BinaryExpr); ok { switch binary.Op { case "==", "!=", "<", "<=", ">", ">=", "&&", "||": typ = NamedType{Name: "Boolean"} default: if meta := exprMeta(binary.Left); meta != nil { typ = meta.Type } } } meaning, symbol := resolver.meaning(expr, scope) if ident, ok := expr.(IdentExpr); ok && meaning == UnresolvedExpr && isUnknownType(typ) && !isSemanticBuiltin(ident.Name) { resolver.addDiagnostic(undefinedDiagnostic(ident.Name, ident.Pos)) } resolver.validateEnumExpression(expr, meaning) resolver.validateGenericCall(expr) if meaning == GoCallExpr && isResultType(expected) { typ = expected } if nullable, ok := typ.(NullableType); ok && typeEqual(nullable.Element, expected) { typ = expected } semantic := &HIRExpr{ID: len(resolver.program.HIR.Expressions) + 1, Type: typ, Meaning: meaning, Symbol: symbol} semantic.Node = resolver.hirNode(expr, semantic) resolver.program.HIR.Expressions = append(resolver.program.HIR.Expressions, semantic) return withExprMeta(expr, semantic), typ } func (resolver *semanticResolver) hirNode(expr Expr, semantic *HIRExpr) HIRNode { switch semantic.Meaning { case LiteralExpr: return HIRLiteral{} case LocalReferenceExpr, FunctionReferenceExpr, ClassReferenceExpr, EnumReferenceExpr, ImportReferenceExpr, FieldAccessExpr: return HIRReference{Target: semantic.Symbol} case GoCallExpr: if call, ok := expr.(CallExpr); ok { signature, _ := resolver.program.goCallSignature(call) return HIRGoCall{Callee: exprMeta(call.Callee), Result: semantic.Type, Params: signature.Params, Variadic: signature.Variadic, InjectContext: shouldInjectCoroutineContext(call, signature)} } case GotlinCallExpr, MethodCallExpr: return HIRGotlinCall{Target: semantic.Symbol, Result: semantic.Type} case ClassConstructionExpr: if class := classTypeOf(semantic.Type); class != nil { return HIRClassConstruction{Class: class} } case EnumConstructionExpr: enumName, variantName := enumExpressionName(expr) return HIREnumConstruction{EnumName: enumName, VariantName: variantName} case PropagateResultExpr: if attempt, ok := expr.(TryExpr); ok { return HIRPropagateResult{Value: exprMeta(attempt.Value), Type: semantic.Type} } case MatchValueExpr: if match, ok := expr.(MatchExpr); ok { cases := make([]HIRMatchCase, len(match.Cases)) for index, matchCase := range match.Cases { cases[index] = HIRMatchCase{EnumName: matchCase.EnumName, VariantName: matchCase.VariantName, Bindings: matchCase.Bindings, Value: exprMeta(matchCase.Value)} } return HIRMatch{Value: exprMeta(match.Value), Cases: cases, Type: semantic.Type} } case CoroutineExpression: if call, ok := expr.(CallExpr); ok { if ident, ok := call.Callee.(IdentExpr); ok { return HIRCoroutine{Operation: ident.Name} } } case SQLExpression: return HIRSQL{Type: semantic.Type} case MappingExpression: if call, ok := expr.(CallExpr); ok { if selector, ok := call.Callee.(SelectorExpr); ok { target := semantic.Type return HIRMapping{Source: ResolvedType(selector.Receiver), Target: target} } } } return nil } func enumExpressionName(expr Expr) (string, string) { switch value := expr.(type) { case CallExpr: if selector, ok := value.Callee.(SelectorExpr); ok { if receiver, ok := selector.Receiver.(IdentExpr); ok { return receiver.Name, selector.Name } } case SelectorExpr: if receiver, ok := value.Receiver.(IdentExpr); ok { return receiver.Name, value.Name } case EnumVariantExpr: return value.EnumName, value.VariantName } return "", "" } func (resolver *semanticResolver) meaning(expr Expr, scope *Scope) (ExprMeaning, *Symbol) { switch value := expr.(type) { case IntExpr, FloatExpr, StringExpr, BoolExpr, NullExpr: return LiteralExpr, nil case IdentExpr: if symbol, ok := scope.Lookup(value.Name); ok { switch symbol.Kind { case FunctionSymbolKind: return FunctionReferenceExpr, symbol case ClassSymbolKind: return ClassReferenceExpr, symbol case EnumSymbolKind: return EnumReferenceExpr, symbol case ImportSymbolKind: return ImportReferenceExpr, symbol default: return LocalReferenceExpr, symbol } } case CallExpr: if _, _, _, ok := splitSQLChain(value); ok { return SQLExpression, nil } if _, ok := value.Callee.(SelectorExpr); ok { selector := value.Callee.(SelectorExpr) if receiver, ok := selector.Receiver.(IdentExpr); ok && receiver.Name == "json" && selector.Name == "decode" { return GotlinCallExpr, nil } if selector.Name == "mapTo" { return MappingExpression, nil } if receiver, ok := selector.Receiver.(IdentExpr); ok { if pack := resolver.program.Packages[receiver.Name]; pack != nil { if function := pack.Function(selector.Name); function != nil { return GotlinCallExpr, function } } if receiver.Name == "Result" { return EnumConstructionExpr, nil } if _, ok := resolver.program.Enums[receiver.Name]; ok { return EnumConstructionExpr, nil } } if root, ok := selectorRootAlias(selector); ok && resolver.program.Imports[root] { return GoCallExpr, nil } if class, _ := classInstance(ResolvedType(selector.Receiver)); class != nil { if method := class.Method(selector.Name); method != nil { return MethodCallExpr, method } } receiverType := ResolvedType(selector.Receiver) if !isUnknownType(resolver.program.goMethodType(receiverType, selector.Name)) { return GoCallExpr, nil } return MethodCallExpr, nil } if ident, ok := value.Callee.(IdentExpr); ok { if coroutineBuiltins[ident.Name] { return CoroutineExpression, nil } if symbol, ok := resolver.program.Global.Lookup(ident.Name); ok { if symbol.Kind == ClassSymbolKind { return ClassConstructionExpr, symbol } if symbol.Kind == FunctionSymbolKind { return GotlinCallExpr, symbol } } } return GoCallExpr, nil case SelectorExpr: if receiver, ok := value.Receiver.(IdentExpr); ok { if _, ok := resolver.program.Enums[receiver.Name]; ok { return EnumReferenceExpr, nil } } return FieldAccessExpr, nil case SafeSelectorExpr: return FieldAccessExpr, nil case EnumVariantExpr: return EnumConstructionExpr, nil case TryExpr: return PropagateResultExpr, nil case MatchExpr: return MatchValueExpr, nil } return UnresolvedExpr, nil } func (resolver *semanticResolver) defineMatchBindings(scope *Scope, enumName, variantName string, bindings []string) { decl, ok := resolver.program.Enums[enumName] if !ok { return } variant := enumVariant(decl, variantName) if variant == nil { return } for index, binding := range bindings { if index >= len(variant.PayloadTypes) { break } typ, _ := resolver.program.ResolveType(variant.PayloadTypes[index]) _ = scope.Define(&Symbol{Name: binding, Kind: VariableSymbol, Type: typ}) } } func (resolver *semanticResolver) validateStatementMatch(match MatchStmt) { patterns := make([]matchPattern, len(match.Cases)) for index, matchCase := range match.Cases { patterns[index] = matchPattern{enumName: matchCase.EnumName, variantName: matchCase.VariantName, bindings: matchCase.Bindings} } resolver.validateMatchPatterns(patterns) } func (resolver *semanticResolver) validateValueMatch(match MatchExpr) { patterns := make([]matchPattern, len(match.Cases)) var result Type = UnknownType{} for index, matchCase := range match.Cases { patterns[index] = matchPattern{enumName: matchCase.EnumName, variantName: matchCase.VariantName, bindings: matchCase.Bindings} armType := ResolvedType(matchCase.Value) if isUnknownType(result) { result = armType } else if !isUnknownType(armType) && !typeEqual(result, armType) { resolver.fail(fmt.Errorf("match expression arm %s.%s has type %s, expected %s", matchCase.EnumName, matchCase.VariantName, armType.String(), result.String())) } } if isUnknownType(result) { resolver.fail(fmt.Errorf("match expression result type cannot be inferred")) } resolver.validateMatchPatterns(patterns) } type matchPattern struct { enumName, variantName string bindings []string } func (resolver *semanticResolver) validateMatchPatterns(patterns []matchPattern) { if len(patterns) == 0 { resolver.fail(fmt.Errorf("match requires at least one case")) return } enumName := patterns[0].enumName decl, ok := resolver.program.Enums[enumName] if !ok { resolver.fail(fmt.Errorf("match value is not a known enum")) return } seen := map[string]bool{} for _, pattern := range patterns { if pattern.enumName != enumName { resolver.fail(fmt.Errorf("match case %s.%s does not match enum %s", pattern.enumName, pattern.variantName, enumName)) return } if seen[pattern.variantName] { resolver.fail(fmt.Errorf("duplicate match case %s.%s", enumName, pattern.variantName)) return } seen[pattern.variantName] = true variant := enumVariant(decl, pattern.variantName) if variant == nil { resolver.fail(fmt.Errorf("unknown variant %s.%s", enumName, pattern.variantName)) return } if len(pattern.bindings) != len(variant.PayloadTypes) { resolver.fail(fmt.Errorf("match case %s.%s expects %d bindings", enumName, pattern.variantName, len(variant.PayloadTypes))) return } } for _, variant := range decl.Variants { if !seen[variant.Name] { resolver.fail(fmt.Errorf("non-exhaustive match for %s: missing %s", enumName, variant.Name)) return } } } func (resolver *semanticResolver) fail(err error) { if resolver.err == nil { resolver.err = err } } func (resolver *semanticResolver) addDiagnostic(diagnostic SemanticDiagnostic) { for _, existing := range resolver.program.Diagnostics { if existing.Code == diagnostic.Code && existing.Span.Start == diagnostic.Span.Start && existing.Message == diagnostic.Message { return } } resolver.program.Diagnostics = append(resolver.program.Diagnostics, diagnostic) } func isSemanticBuiltin(name string) bool { return semanticBuiltins[name] } var semanticBuiltins = map[string]bool{ "println": true, "runCatching": true, "Channel": true, "listOf": true, "mutableListOf": true, "mapOf": true, "mutableMapOf": true, "append": true, "keys": true, "goAssert": true, "len": true, "cap": true, "make": true, "new": true, "copy": true, "delete": true, "close": true, "panic": true, "recover": true, "string": true, "int": true, "float64": true, "bool": true, "sql": true, "set": true, "now": true, "Result": true, "ByteSlice": true, "runBlocking": true, "coroutineScope": true, "launch": true, "async": true, "delay": true, "withTimeout": true, "isActive": true, "coroutineContext": true, "continue": true, "break": true, } func (resolver *semanticResolver) validateEnumExpression(expr Expr, meaning ExprMeaning) { if meaning != EnumConstructionExpr { return } var enumName, variantName string valueCount := 0 switch value := expr.(type) { case CallExpr: selector, ok := value.Callee.(SelectorExpr) if !ok { return } receiver, ok := selector.Receiver.(IdentExpr) if !ok { return } enumName, variantName, valueCount = receiver.Name, selector.Name, len(value.Args) case SelectorExpr: receiver, ok := value.Receiver.(IdentExpr) if !ok { return } enumName, variantName = receiver.Name, value.Name default: return } if enumName == "Result" { if valueCount != 1 { resolver.fail(fmt.Errorf("Result.%s expects one value", variantName)) } return } decl, ok := resolver.program.Enums[enumName] if !ok { return } variant := enumVariant(decl, variantName) if variant == nil { resolver.fail(fmt.Errorf("unknown variant %s.%s", enumName, variantName)) return } if len(variant.PayloadTypes) != valueCount { resolver.fail(fmt.Errorf("variant %s.%s expects %d values", enumName, variantName, len(variant.PayloadTypes))) } } func (resolver *semanticResolver) validateGenericCall(expr Expr) { call, ok := expr.(CallExpr) if !ok { return } ident, ok := call.Callee.(IdentExpr) if !ok { return } var params []string found := false if function, ok := resolver.program.Functions[ident.Name]; ok { params = function.TypeParams found = true } if class, ok := resolver.program.ClassInfo[ident.Name]; ok { params = class.TypeParams found = true } if found && len(call.TypeArgs) > 0 && len(call.TypeArgs) != len(params) { resolver.fail(fmt.Errorf("%s expects %d type arguments, got %d", ident.Name, len(params), len(call.TypeArgs))) } } func collectionElement(typ Type) Type { if generic, ok := typ.(GenericType); ok && len(generic.Args) > 0 { return generic.Args[len(generic.Args)-1] } return UnknownType{} } func functionResult(typ Type) Type { if function, ok := typ.(FunctionType); ok { return function.Result } return UnknownType{} } func isResultType(typ Type) bool { result, ok := typ.(GenericType) return ok && result.Base.String() == "Result" && len(result.Args) == 2 }