Add value-returning match expressions

This commit is contained in:
pavel 2026-08-27 17:56:13 +02:00
commit ab783c33ed
12 changed files with 384 additions and 14 deletions

View file

@ -1679,6 +1679,8 @@ func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) {
return "", err
}
return receiver + "[" + index + "]", nil
case MatchExpr:
return g.valueMatch(e, expectedType)
case EnumVariantExpr:
if e.EnumName == "Result" {
base, args, ok := parseGenericType(expectedType)
@ -1743,6 +1745,128 @@ func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) {
}
}
func (g *goGenerator) valueMatch(match MatchExpr, expectedType string) (string, error) {
enumName := strings.TrimPrefix(g.exprType(match.Value), "*")
if enumName == "" && len(match.Cases) > 0 {
enumName = match.Cases[0].EnumName
}
decl, ok := g.enums[enumName]
if !ok {
return "", fmt.Errorf("match value is not a known enum")
}
seen := map[string]bool{}
for _, c := range match.Cases {
if c.EnumName != enumName {
return "", fmt.Errorf("match case %s::%s does not match enum %s", c.EnumName, c.VariantName, enumName)
}
if seen[c.VariantName] {
return "", fmt.Errorf("duplicate match case %s::%s", enumName, c.VariantName)
}
seen[c.VariantName] = true
variant := enumVariant(decl, c.VariantName)
if variant == nil {
return "", fmt.Errorf("unknown variant %s::%s", enumName, c.VariantName)
}
if len(c.Bindings) != len(variant.PayloadTypes) {
return "", fmt.Errorf("match case %s::%s expects %d bindings", enumName, c.VariantName, len(variant.PayloadTypes))
}
}
for _, variant := range decl.Variants {
if !seen[variant.Name] {
return "", fmt.Errorf("non-exhaustive match for %s: missing %s", enumName, variant.Name)
}
}
resultType := expectedType
if resultType == "" || resultType == "Any" {
for _, c := range match.Cases {
variant := enumVariant(decl, c.VariantName)
g.pushScope()
for i, binding := range c.Bindings {
g.defineType(binding, variant.PayloadTypes[i])
}
armType := g.exprType(c.Value)
g.popScope()
if armType != "" {
resultType = armType
break
}
}
}
if resultType == "" || resultType == "Unit" {
return "", fmt.Errorf("match expression result type cannot be inferred")
}
for _, c := range match.Cases {
variant := enumVariant(decl, c.VariantName)
g.pushScope()
for i, binding := range c.Bindings {
g.defineType(binding, variant.PayloadTypes[i])
}
armType := g.exprType(c.Value)
g.popScope()
if armType != "" && armType != resultType {
return "", fmt.Errorf("match expression arm %s::%s has type %s, expected %s", enumName, c.VariantName, armType, resultType)
}
}
value, err := g.expr(match.Value, enumName)
if err != nil {
return "", err
}
g.matchCounter++
matchName := fmt.Sprintf("gotlinMatch%d", g.matchCounter)
var out strings.Builder
out.WriteString("func() ")
out.WriteString(mapGoType(resultType))
out.WriteString(" { ")
if enumIsString(decl) {
out.WriteString("switch ")
out.WriteString(value)
out.WriteString(" { ")
for _, c := range match.Cases {
arm, err := g.expr(c.Value, resultType)
if err != nil {
return "", err
}
out.WriteString("case ")
out.WriteString(enumName + c.VariantName)
out.WriteString(": return ")
out.WriteString(arm)
out.WriteString("; ")
}
out.WriteString("}; ")
} else {
out.WriteString("switch ")
out.WriteString(matchName)
out.WriteString(" := ")
out.WriteString(value)
out.WriteString(".(type) { ")
for _, c := range match.Cases {
variant := enumVariant(decl, c.VariantName)
g.pushScope()
out.WriteString("case *")
out.WriteString(enumName + c.VariantName)
out.WriteString(": ")
for i, binding := range c.Bindings {
g.defineType(binding, variant.PayloadTypes[i])
out.WriteString(binding)
out.WriteString(" := ")
out.WriteString(matchName)
out.WriteString(fmt.Sprintf(".Value%d; ", i))
}
arm, err := g.expr(c.Value, resultType)
g.popScope()
if err != nil {
return "", err
}
out.WriteString("return ")
out.WriteString(arm)
out.WriteString("; ")
}
out.WriteString("}; ")
}
out.WriteString(`panic("unreachable exhaustive match") }()`)
return out.String(), nil
}
func (g *goGenerator) emitJSONDecodeSupport() {
g.line("func gotlinJSONDecode[T any](body []byte) GotlinResult[T] {")
g.indentLevel++
@ -2883,6 +3007,22 @@ func exprUsesName(expr Expr, name string, shadowed bool) bool {
}
}
return false
case MatchExpr:
if exprUsesName(e.Value, name, shadowed) {
return true
}
for _, matchCase := range e.Cases {
caseShadowed := shadowed
for _, binding := range matchCase.Bindings {
if binding == name {
caseShadowed = true
}
}
if exprUsesName(matchCase.Value, name, caseShadowed) {
return true
}
}
return false
case LambdaExpr:
lambdaShadowed := shadowed
if e.ImplicitIt && name == "it" {
@ -3044,6 +3184,12 @@ func (g *goGenerator) exprType(expr Expr) string {
if _, args, ok := parseGenericType(g.exprType(e.Receiver)); ok && len(args) > 0 {
return args[len(args)-1]
}
case MatchExpr:
for _, c := range e.Cases {
if typ := g.exprType(c.Value); typ != "" {
return typ
}
}
case EnumVariantExpr:
return e.EnumName
case NonNullExpr:
@ -3280,6 +3426,15 @@ func exprUsesPrintln(expr Expr) bool {
return exprUsesPrintln(e.Left) || exprUsesPrintln(e.Right)
case SelectorExpr:
return exprUsesPrintln(e.Receiver)
case MatchExpr:
if exprUsesPrintln(e.Value) {
return true
}
for _, matchCase := range e.Cases {
if exprUsesPrintln(matchCase.Value) {
return true
}
}
case LambdaExpr:
return usesPrintln(e.Body)
}
@ -3306,6 +3461,15 @@ func exprUsesRunCatching(expr Expr) bool {
return exprUsesRunCatching(e.Left) || exprUsesRunCatching(e.Right)
case SelectorExpr:
return exprUsesRunCatching(e.Receiver)
case MatchExpr:
if exprUsesRunCatching(e.Value) {
return true
}
for _, matchCase := range e.Cases {
if exprUsesRunCatching(matchCase.Value) {
return true
}
}
case LambdaExpr:
return usesRunCatching(e.Body)
}
@ -3332,6 +3496,15 @@ func exprUsesTimerBuiltins(expr Expr) bool {
return exprUsesTimerBuiltins(e.Left) || exprUsesTimerBuiltins(e.Right)
case SelectorExpr:
return exprUsesTimerBuiltins(e.Receiver)
case MatchExpr:
if exprUsesTimerBuiltins(e.Value) {
return true
}
for _, matchCase := range e.Cases {
if exprUsesTimerBuiltins(matchCase.Value) {
return true
}
}
case LambdaExpr:
return usesTimerBuiltins(e.Body)
}
@ -3358,6 +3531,15 @@ func exprUsesEveryBuiltin(expr Expr) bool {
return exprUsesEveryBuiltin(e.Left) || exprUsesEveryBuiltin(e.Right)
case SelectorExpr:
return exprUsesEveryBuiltin(e.Receiver)
case MatchExpr:
if exprUsesEveryBuiltin(e.Value) {
return true
}
for _, matchCase := range e.Cases {
if exprUsesEveryBuiltin(matchCase.Value) {
return true
}
}
case LambdaExpr:
return usesEveryBuiltin(e.Body)
}
@ -3485,6 +3667,11 @@ func usedImportAliases(program *Program) map[string]bool {
for _, value := range e.Values {
walkExpr(value)
}
case MatchExpr:
walkExpr(e.Value)
for _, matchCase := range e.Cases {
walkExpr(matchCase.Value)
}
case LambdaExpr:
for _, param := range e.Params {
markType(param.Type)