Add value-returning match expressions
This commit is contained in:
parent
b0a52de4ea
commit
ab783c33ed
12 changed files with 384 additions and 14 deletions
14
README.md
14
README.md
|
|
@ -108,18 +108,18 @@ enum PaymentResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun describe(result: PaymentResult): String {
|
fun describe(result: PaymentResult): String {
|
||||||
var description = ""
|
return match (result) {
|
||||||
match (result) {
|
PaymentResult::Accepted(id) -> id
|
||||||
PaymentResult::Accepted(id) -> { description = id }
|
PaymentResult::Rejected(reason) -> reason
|
||||||
PaymentResult::Rejected(reason) -> { description = reason }
|
PaymentResult::Pending -> "pending"
|
||||||
PaymentResult::Pending -> { description = "pending" }
|
|
||||||
}
|
}
|
||||||
return description
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Enum matches must contain each variant exactly once. Variant payload arity is
|
Enum matches must contain each variant exactly once. Variant payload arity is
|
||||||
checked during Gotlin compilation.
|
checked during Gotlin compilation. A match used as an expression also requires
|
||||||
|
every arm to return the same type. Block-style statement matches remain
|
||||||
|
available for side effects.
|
||||||
|
|
||||||
## Null safety
|
## Null safety
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,11 @@ enum PaymentResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun describe(result: PaymentResult): String {
|
fun describe(result: PaymentResult): String {
|
||||||
var description = ""
|
return match (result) {
|
||||||
match (result) {
|
PaymentResult::Accepted(id) -> "accepted " + id
|
||||||
PaymentResult::Accepted(id) -> { description = "accepted " + id }
|
PaymentResult::Rejected(reason) -> "rejected " + reason
|
||||||
PaymentResult::Rejected(reason) -> { description = "rejected " + reason }
|
PaymentResult::Pending -> "pending"
|
||||||
PaymentResult::Pending -> { description = "pending" }
|
|
||||||
}
|
}
|
||||||
return description
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,19 @@ type MatchCase struct {
|
||||||
Body []Stmt
|
Body []Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MatchExpr struct {
|
||||||
|
Value Expr
|
||||||
|
Cases []MatchExprCase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MatchExpr) exprNode() {}
|
||||||
|
|
||||||
|
type MatchExprCase struct {
|
||||||
|
EnumName, VariantName string
|
||||||
|
Bindings []string
|
||||||
|
Value Expr
|
||||||
|
}
|
||||||
|
|
||||||
type SelectCase struct {
|
type SelectCase struct {
|
||||||
Source Expr
|
Source Expr
|
||||||
Body []Stmt
|
Body []Stmt
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,15 @@ func expressionUsesCoroutines(expr Expr) bool {
|
||||||
}
|
}
|
||||||
case LambdaExpr:
|
case LambdaExpr:
|
||||||
return statementsUseCoroutines(e.Body)
|
return statementsUseCoroutines(e.Body)
|
||||||
|
case MatchExpr:
|
||||||
|
if expressionUsesCoroutines(e.Value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, matchCase := range e.Cases {
|
||||||
|
if expressionUsesCoroutines(matchCase.Value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
case SelectorExpr:
|
case SelectorExpr:
|
||||||
return expressionUsesCoroutines(e.Receiver)
|
return expressionUsesCoroutines(e.Receiver)
|
||||||
case BinaryExpr:
|
case BinaryExpr:
|
||||||
|
|
|
||||||
|
|
@ -77,3 +77,68 @@ fun main() { println(Status::PendingReservation) }`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGenerateExhaustiveMatchExpression(t *testing.T) {
|
||||||
|
prog, err := Parse(`package demo
|
||||||
|
enum AccountType { BASIC, SAVINGS }
|
||||||
|
fun interestRate(accountType: AccountType): Double {
|
||||||
|
return match (accountType) {
|
||||||
|
AccountType::BASIC -> 0.0
|
||||||
|
AccountType::SAVINGS -> 0.02
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
out, err := GenerateGo(prog)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, want := range []string{"func() float64", "case AccountTypeBASIC:", "return 0.0", "case AccountTypeSAVINGS:", "return 0.02", `panic("unreachable exhaustive match")`} {
|
||||||
|
if !strings.Contains(string(out), want) {
|
||||||
|
t.Fatalf("missing %q:\n%s", want, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGeneratePayloadMatchExpression(t *testing.T) {
|
||||||
|
prog, err := Parse(`package demo
|
||||||
|
enum Outcome { Success(String), Failure(String) }
|
||||||
|
fun message(outcome: Outcome): String {
|
||||||
|
return match (outcome) {
|
||||||
|
Outcome::Success(value) -> value
|
||||||
|
Outcome::Failure(reason) -> reason
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
out, err := GenerateGo(prog)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, want := range []string{"switch gotlinMatch", "value := gotlinMatch", "return value", "return reason"} {
|
||||||
|
if !strings.Contains(string(out), want) {
|
||||||
|
t.Fatalf("missing %q:\n%s", want, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRejectInvalidMatchExpression(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
source string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{`package demo enum State { On, Off } fun value(state: State): Int { return match (state) { State::On -> 1 } }`, "missing Off"},
|
||||||
|
{`package demo enum State { On, Off } fun value(state: State): Int { return match (state) { State::On -> 1 State::Off -> "off" } }`, "has type String, expected Int"},
|
||||||
|
} {
|
||||||
|
prog, err := Parse(test.source)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = GenerateGo(prog)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), test.want) {
|
||||||
|
t.Fatalf("expected %q, got %v", test.want, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1679,6 +1679,8 @@ func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return receiver + "[" + index + "]", nil
|
return receiver + "[" + index + "]", nil
|
||||||
|
case MatchExpr:
|
||||||
|
return g.valueMatch(e, expectedType)
|
||||||
case EnumVariantExpr:
|
case EnumVariantExpr:
|
||||||
if e.EnumName == "Result" {
|
if e.EnumName == "Result" {
|
||||||
base, args, ok := parseGenericType(expectedType)
|
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() {
|
func (g *goGenerator) emitJSONDecodeSupport() {
|
||||||
g.line("func gotlinJSONDecode[T any](body []byte) GotlinResult[T] {")
|
g.line("func gotlinJSONDecode[T any](body []byte) GotlinResult[T] {")
|
||||||
g.indentLevel++
|
g.indentLevel++
|
||||||
|
|
@ -2883,6 +3007,22 @@ func exprUsesName(expr Expr, name string, shadowed bool) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
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:
|
case LambdaExpr:
|
||||||
lambdaShadowed := shadowed
|
lambdaShadowed := shadowed
|
||||||
if e.ImplicitIt && name == "it" {
|
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 {
|
if _, args, ok := parseGenericType(g.exprType(e.Receiver)); ok && len(args) > 0 {
|
||||||
return args[len(args)-1]
|
return args[len(args)-1]
|
||||||
}
|
}
|
||||||
|
case MatchExpr:
|
||||||
|
for _, c := range e.Cases {
|
||||||
|
if typ := g.exprType(c.Value); typ != "" {
|
||||||
|
return typ
|
||||||
|
}
|
||||||
|
}
|
||||||
case EnumVariantExpr:
|
case EnumVariantExpr:
|
||||||
return e.EnumName
|
return e.EnumName
|
||||||
case NonNullExpr:
|
case NonNullExpr:
|
||||||
|
|
@ -3280,6 +3426,15 @@ func exprUsesPrintln(expr Expr) bool {
|
||||||
return exprUsesPrintln(e.Left) || exprUsesPrintln(e.Right)
|
return exprUsesPrintln(e.Left) || exprUsesPrintln(e.Right)
|
||||||
case SelectorExpr:
|
case SelectorExpr:
|
||||||
return exprUsesPrintln(e.Receiver)
|
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:
|
case LambdaExpr:
|
||||||
return usesPrintln(e.Body)
|
return usesPrintln(e.Body)
|
||||||
}
|
}
|
||||||
|
|
@ -3306,6 +3461,15 @@ func exprUsesRunCatching(expr Expr) bool {
|
||||||
return exprUsesRunCatching(e.Left) || exprUsesRunCatching(e.Right)
|
return exprUsesRunCatching(e.Left) || exprUsesRunCatching(e.Right)
|
||||||
case SelectorExpr:
|
case SelectorExpr:
|
||||||
return exprUsesRunCatching(e.Receiver)
|
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:
|
case LambdaExpr:
|
||||||
return usesRunCatching(e.Body)
|
return usesRunCatching(e.Body)
|
||||||
}
|
}
|
||||||
|
|
@ -3332,6 +3496,15 @@ func exprUsesTimerBuiltins(expr Expr) bool {
|
||||||
return exprUsesTimerBuiltins(e.Left) || exprUsesTimerBuiltins(e.Right)
|
return exprUsesTimerBuiltins(e.Left) || exprUsesTimerBuiltins(e.Right)
|
||||||
case SelectorExpr:
|
case SelectorExpr:
|
||||||
return exprUsesTimerBuiltins(e.Receiver)
|
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:
|
case LambdaExpr:
|
||||||
return usesTimerBuiltins(e.Body)
|
return usesTimerBuiltins(e.Body)
|
||||||
}
|
}
|
||||||
|
|
@ -3358,6 +3531,15 @@ func exprUsesEveryBuiltin(expr Expr) bool {
|
||||||
return exprUsesEveryBuiltin(e.Left) || exprUsesEveryBuiltin(e.Right)
|
return exprUsesEveryBuiltin(e.Left) || exprUsesEveryBuiltin(e.Right)
|
||||||
case SelectorExpr:
|
case SelectorExpr:
|
||||||
return exprUsesEveryBuiltin(e.Receiver)
|
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:
|
case LambdaExpr:
|
||||||
return usesEveryBuiltin(e.Body)
|
return usesEveryBuiltin(e.Body)
|
||||||
}
|
}
|
||||||
|
|
@ -3485,6 +3667,11 @@ func usedImportAliases(program *Program) map[string]bool {
|
||||||
for _, value := range e.Values {
|
for _, value := range e.Values {
|
||||||
walkExpr(value)
|
walkExpr(value)
|
||||||
}
|
}
|
||||||
|
case MatchExpr:
|
||||||
|
walkExpr(e.Value)
|
||||||
|
for _, matchCase := range e.Cases {
|
||||||
|
walkExpr(matchCase.Value)
|
||||||
|
}
|
||||||
case LambdaExpr:
|
case LambdaExpr:
|
||||||
for _, param := range e.Params {
|
for _, param := range e.Params {
|
||||||
markType(param.Type)
|
markType(param.Type)
|
||||||
|
|
|
||||||
|
|
@ -793,6 +793,68 @@ func (p *parser) parseMatch() (Stmt, error) {
|
||||||
return MatchStmt{Value: value, Cases: cases}, nil
|
return MatchStmt{Value: value, Cases: cases}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *parser) parseMatchExpr() (Expr, error) {
|
||||||
|
if _, err := p.expect(tokenLParen, "expected '(' after match"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
value, err := p.parseExpr(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := p.expect(tokenRParen, "expected ')' after match value"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := p.expect(tokenLBrace, "expected '{' after match value"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var cases []MatchExprCase
|
||||||
|
for !p.check(tokenRBrace) && !p.check(tokenEOF) {
|
||||||
|
enumName, err := p.expect(tokenIdent, "expected enum name in match case")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := p.expect(tokenDoubleColon, "expected '::' in match case"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
variant, err := p.expect(tokenIdent, "expected variant name")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var bindings []string
|
||||||
|
if p.match(tokenLParen) {
|
||||||
|
if !p.check(tokenRParen) {
|
||||||
|
for {
|
||||||
|
binding, err := p.expect(tokenIdent, "expected variant binding")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bindings = append(bindings, binding.lexeme)
|
||||||
|
if !p.match(tokenComma) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := p.expect(tokenRParen, "expected ')' after variant bindings"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := p.expect(tokenArrow, "expected '->' after match pattern"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result, err := p.parseExpr(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cases = append(cases, MatchExprCase{EnumName: enumName.lexeme, VariantName: variant.lexeme, Bindings: bindings, Value: result})
|
||||||
|
p.match(tokenComma)
|
||||||
|
p.match(tokenSemicolon)
|
||||||
|
}
|
||||||
|
if _, err := p.expect(tokenRBrace, "expected '}' after match"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return MatchExpr{Value: value, Cases: cases}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *parser) parseSelect() (Stmt, error) {
|
func (p *parser) parseSelect() (Stmt, error) {
|
||||||
if _, err := p.expect(tokenLBrace, "expected '{' after select"); err != nil {
|
if _, err := p.expect(tokenLBrace, "expected '{' after select"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -1016,6 +1078,8 @@ func (p *parser) parsePrefix() (Expr, error) {
|
||||||
return p.parsePostfix(expr)
|
return p.parsePostfix(expr)
|
||||||
case tokenLBrace:
|
case tokenLBrace:
|
||||||
return p.parseLambdaExpr()
|
return p.parseLambdaExpr()
|
||||||
|
case tokenMatch:
|
||||||
|
return p.parseMatchExpr()
|
||||||
case tokenBang, tokenMinus, tokenAmp, tokenStar:
|
case tokenBang, tokenMinus, tokenAmp, tokenStar:
|
||||||
value, err := p.parseExpr(7)
|
value, err := p.parseExpr(7)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,11 @@ func normalizeExprTypes(expression Expr, normalize func(string) string) {
|
||||||
for _, item := range value.Values {
|
for _, item := range value.Values {
|
||||||
normalizeExprTypes(item, normalize)
|
normalizeExprTypes(item, normalize)
|
||||||
}
|
}
|
||||||
|
case MatchExpr:
|
||||||
|
normalizeExprTypes(value.Value, normalize)
|
||||||
|
for _, item := range value.Cases {
|
||||||
|
normalizeExprTypes(item.Value, normalize)
|
||||||
|
}
|
||||||
case LambdaExpr:
|
case LambdaExpr:
|
||||||
for i := range value.Params {
|
for i := range value.Params {
|
||||||
value.Params[i].Type = normalize(value.Params[i].Type)
|
value.Params[i].Type = normalize(value.Params[i].Type)
|
||||||
|
|
|
||||||
|
|
@ -225,6 +225,15 @@ func (c *mutabilityChecker) checkExpr(expr Expr) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case MatchExpr:
|
||||||
|
if err := c.checkExpr(e.Value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, matchCase := range e.Cases {
|
||||||
|
if err := c.checkExpr(matchCase.Value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
case LambdaExpr:
|
case LambdaExpr:
|
||||||
bindings := map[string]bool{}
|
bindings := map[string]bool{}
|
||||||
if e.ImplicitIt {
|
if e.ImplicitIt {
|
||||||
|
|
|
||||||
|
|
@ -1144,6 +1144,15 @@ func exprMatches(expr Expr, match func(Expr) bool) bool {
|
||||||
return exprMatches(e.Receiver, match)
|
return exprMatches(e.Receiver, match)
|
||||||
case IndexExpr:
|
case IndexExpr:
|
||||||
return exprMatches(e.Receiver, match) || exprMatches(e.Index, match)
|
return exprMatches(e.Receiver, match) || exprMatches(e.Index, match)
|
||||||
|
case MatchExpr:
|
||||||
|
if exprMatches(e.Value, match) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, matchCase := range e.Cases {
|
||||||
|
if exprMatches(matchCase.Value, match) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
case LambdaExpr:
|
case LambdaExpr:
|
||||||
return stmtsMatch(e.Body, match)
|
return stmtsMatch(e.Body, match)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ for (const declaration of [
|
||||||
|
|
||||||
const prefixes = new Set(Object.values(snippets).map((snippet) => snippet.prefix));
|
const prefixes = new Set(Object.values(snippets).map((snippet) => snippet.prefix));
|
||||||
for (const prefix of [
|
for (const prefix of [
|
||||||
"dataclass", "tablerow", "embed", "coroutinescope", "launch", "async", "enum", "match", "mapto", "safe", "nonnull", "resultfun", "resultmatch", "defer", "foreach", "sqlfetch",
|
"dataclass", "tablerow", "embed", "coroutinescope", "launch", "async", "enum", "match", "matchvalue", "mapto", "safe", "nonnull", "resultfun", "resultmatch", "defer", "foreach", "sqlfetch",
|
||||||
"sqlsingle", "sqliterator", "sqlinsertnothing", "sqlinsertupdate", "sqlupdatereturning"
|
"sqlsingle", "sqliterator", "sqlinsertnothing", "sqlinsertupdate", "sqlupdatereturning"
|
||||||
]) {
|
]) {
|
||||||
assert(prefixes.has(prefix), `snippets are missing prefix ${prefix}`);
|
assert(prefixes.has(prefix), `snippets are missing prefix ${prefix}`);
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,17 @@
|
||||||
],
|
],
|
||||||
"description": "Exhaustive match over enum variants"
|
"description": "Exhaustive match over enum variants"
|
||||||
},
|
},
|
||||||
|
"Exhaustive Match Expression": {
|
||||||
|
"prefix": "matchvalue",
|
||||||
|
"body": [
|
||||||
|
"val ${1:value} = match (${2:result}) {",
|
||||||
|
" ${3:Result}::${4:Success}(${5:item}) -> ${5:item}",
|
||||||
|
" ${3:Result}::${6:Failure}(${7:reason}) -> ${7:reason}",
|
||||||
|
" $0",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Return a typed value from an exhaustive enum match"
|
||||||
|
},
|
||||||
"Recursive Structural Mapping": {
|
"Recursive Structural Mapping": {
|
||||||
"prefix": "mapto",
|
"prefix": "mapto",
|
||||||
"body": ["val ${1:target} = ${2:source}.mapTo<${3:Target}>()"],
|
"body": ["val ${1:target} = ${2:source}.mapTo<${3:Target}>()"],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue