@@ -707,9 +707,33 @@ func transformErrorPropStatements(src []byte, originalSrc []byte, filename strin
707707 result := src
708708 var lineMappings []sourcemap.LineMapping
709709 var columnMappings []sourcemap.ColumnMapping
710- // Start counter at len(locations) and decrement, so first statement in source
711- // gets tmp/err, second gets tmp1/err1, etc. (we process end-to-beginning)
712- counter := len (locations )
710+ // Count only the locations that generate fresh (counter-named) variables.
711+ // Locations that use named return parameters directly (isPlainAssign with a named error
712+ // return, or bare statements whose errVar is a named return) do NOT consume a counter slot.
713+ // This ensures: first counter-consuming statement in source gets tmp/err (no suffix),
714+ // second gets tmp1/err1, etc.
715+ counterConsuming := 0
716+ for _ , loc := range locations {
717+ switch loc .Kind {
718+ case ast .StmtErrorPropAssign , ast .StmtErrorPropLet :
719+ if loc .IsPlainAssign {
720+ // Uses named error return directly — no counter slot needed
721+ if codegen .InferEnclosingFunctionNamedErrorReturn (src , loc .ExprStart ) == "" {
722+ counterConsuming ++
723+ }
724+ } else {
725+ counterConsuming ++
726+ }
727+ case ast .StmtErrorPropBare :
728+ // Uses named error return directly when the function has one — no counter slot needed
729+ if codegen .InferEnclosingFunctionNamedErrorReturn (src , loc .ExprStart ) == "" {
730+ counterConsuming ++
731+ }
732+ default :
733+ // Return statements and other kinds do not generate new variables
734+ }
735+ }
736+ counter := counterConsuming
713737
714738 // First pass: calculate all deltas to know final positions
715739 // We need to track byte deltas from transforms to calculate correct Go positions
@@ -764,7 +788,7 @@ func transformErrorPropStatements(src []byte, originalSrc []byte, filename strin
764788 if loc .TupleLHS != "" {
765789 varNameOrTuple = loc .TupleLHS
766790 }
767- generated = generateErrorPropStatementAdvanced (result , exprBytes , loc .ExprStart , varNameOrTuple , returnTypes , & counter , loc .ErrorKind , loc .ErrorContext , loc .LambdaParam , lambdaBody , resolver )
791+ generated = generateErrorPropStatementAdvanced (result , exprBytes , loc .ExprStart , varNameOrTuple , returnTypes , & counter , loc .ErrorKind , loc .ErrorContext , loc .LambdaParam , lambdaBody , resolver , loc . IsPlainAssign )
768792 // Calculate LHS lengths for column mapping
769793 // Dingo: "varName := " or "a, b := " for tuples
770794 dingoLHSLen = len (varNameOrTuple ) + 4 // " := " or " = " (always 4 with leading space consideration)
@@ -946,7 +970,7 @@ func transformErrorPropStatements(src []byte, originalSrc []byte, filename strin
946970// tmp := expr; if tmp.IsErr() { return dgo.Err[T, E](tmp.MustErr()) }; data := tmp.MustOk()
947971//
948972// Context and Lambda variants wrap the error appropriately.
949- func generateErrorPropStatementAdvanced (src []byte , expr []byte , exprPos int , varName string , returnTypes []string , counter * int , errorKind ast.ErrorPropKind , errorContext string , lambdaParam string , lambdaBody []byte , resolver * codegen.TypeResolver ) []byte {
973+ func generateErrorPropStatementAdvanced (src []byte , expr []byte , exprPos int , varName string , returnTypes []string , counter * int , errorKind ast.ErrorPropKind , errorContext string , lambdaParam string , lambdaBody []byte , resolver * codegen.TypeResolver , isPlainAssign bool ) []byte {
950974 // Check if expression returns a Result type (use resolver for cross-file types)
951975 isResult , exprOkType , exprErrType := codegen .InferExprReturnsResultWithResolver (src , expr , exprPos , resolver )
952976
@@ -955,7 +979,7 @@ func generateErrorPropStatementAdvanced(src []byte, expr []byte, exprPos int, va
955979 }
956980
957981 // Original tuple-based error propagation
958- return generateTupleErrorPropStatement (expr , varName , returnTypes , counter , errorKind , errorContext , lambdaParam , lambdaBody , src , exprPos )
982+ return generateTupleErrorPropStatement (expr , varName , returnTypes , counter , errorKind , errorContext , lambdaParam , lambdaBody , src , exprPos , isPlainAssign )
959983}
960984
961985// generateResultErrorPropStatement generates code for Result[T, E] error propagation.
@@ -1072,35 +1096,63 @@ func generateResultErrorPropStatement(expr []byte, varName string, counter *int,
10721096// Pattern for Result-returning enclosing function:
10731097//
10741098// tmp, err := expr; if err != nil { return dgo.Err[T, E](err) }; data := tmp
1075- func generateTupleErrorPropStatement (expr []byte , varName string , returnTypes []string , counter * int , errorKind ast.ErrorPropKind , errorContext string , lambdaParam string , lambdaBody []byte , src []byte , exprPos int ) []byte {
1099+ func generateTupleErrorPropStatement (expr []byte , varName string , returnTypes []string , counter * int , errorKind ast.ErrorPropKind , errorContext string , lambdaParam string , lambdaBody []byte , src []byte , exprPos int , isPlainAssign bool ) []byte {
10761100 var buf bytes.Buffer
10771101
10781102 // Check if varName contains comma - if so, it's a tuple LHS like "a, b"
10791103 isTupleLHS := strings .Contains (varName , "," )
10801104
1105+ // When isPlainAssign is true, the user wrote `varName = expr?` (plain = not :=).
1106+ // This happens when varName is a named return parameter.
1107+ // In that case, use the actual named error return variable and plain = assignment,
1108+ // instead of counter-generated names and :=.
1109+ var namedErrVar string
1110+ if isPlainAssign {
1111+ namedErrVar = codegen .InferEnclosingFunctionNamedErrorReturn (src , exprPos )
1112+ }
1113+
10811114 // Generate unique variable names
10821115 var tmpVar , errVar string
1083- if * counter == 1 {
1084- tmpVar = "tmp"
1085- errVar = "err"
1116+ if namedErrVar != "" {
1117+ // Plain assign with named return: use named error return directly, don't consume counter slot.
1118+ // The counter is only consumed for generated (counter-named) variables to keep numbering stable.
1119+ errVar = namedErrVar
10861120 } else {
1087- tmpVar = fmt .Sprintf ("tmp%d" , * counter - 1 )
1088- errVar = fmt .Sprintf ("err%d" , * counter - 1 )
1121+ if * counter == 1 {
1122+ tmpVar = "tmp"
1123+ errVar = "err"
1124+ } else {
1125+ tmpVar = fmt .Sprintf ("tmp%d" , * counter - 1 )
1126+ errVar = fmt .Sprintf ("err%d" , * counter - 1 )
1127+ }
1128+ * counter --
10891129 }
1090- * counter --
10911130
10921131 // Check if enclosing function returns Result[T, E]
10931132 // If so, we need to return dgo.Err[T, E](err) instead of tuple
10941133 enclosingReturnsResult , enclosingOkType , enclosingErrType := codegen .InferEnclosingFunctionResultTypes (src , exprPos )
10951134
1096- // For tuple LHS, generate: a, b, err := expr
1097- // For single LHS, generate: tmp, err := expr
1135+ // For tuple LHS, generate: a, b, err := expr (or a, b, err = expr for plain assign)
1136+ // For single LHS with plain assign (named return), generate: varName, err = expr
1137+ // For single LHS with define, generate: tmp, err := expr
10981138 if isTupleLHS {
10991139 // Tuple LHS: fullKey, keyHash, err := util.GeneratePersonalKey()
1140+ declOp := " := "
1141+ if isPlainAssign {
1142+ declOp = " = "
1143+ }
11001144 buf .WriteString (varName )
11011145 buf .WriteString (", " )
11021146 buf .WriteString (errVar )
1103- buf .WriteString (" := " )
1147+ buf .WriteString (declOp )
1148+ buf .Write (expr )
1149+ buf .WriteByte ('\n' )
1150+ } else if isPlainAssign && namedErrVar != "" {
1151+ // Single LHS with named return: result, err = expr
1152+ buf .WriteString (varName )
1153+ buf .WriteString (", " )
1154+ buf .WriteString (errVar )
1155+ buf .WriteString (" = " )
11041156 buf .Write (expr )
11051157 buf .WriteByte ('\n' )
11061158 } else {
@@ -1177,18 +1229,22 @@ func generateTupleErrorPropStatement(expr []byte, varName string, returnTypes []
11771229 }
11781230 }
11791231
1180- buf .WriteString ("\n }\n " )
1181-
11821232 // For tuple LHS, variables are already assigned (fullKey, keyHash, err := expr)
1183- // For single LHS, we need: varName := tmp
1184- if ! isTupleLHS {
1233+ // For single LHS with plain assign (named return), variables are also already assigned (varName, err = expr)
1234+ // For single LHS with define, we need: varName := tmp
1235+ if ! isTupleLHS && ! (isPlainAssign && namedErrVar != "" ) {
1236+ buf .WriteString ("\n }\n " )
11851237 buf .WriteString (varName )
11861238 if varName == "_" {
11871239 buf .WriteString (" = " )
11881240 } else {
11891241 buf .WriteString (" := " )
11901242 }
11911243 buf .WriteString (tmpVar )
1244+ } else {
1245+ // No trailing assignment — end the if block without an extra newline
1246+ // to avoid introducing a blank line before the next source statement.
1247+ buf .WriteString ("\n }" )
11921248 }
11931249
11941250 return buf .Bytes ()
@@ -1698,14 +1754,28 @@ func generateResultErrorPropBare(expr []byte, counter *int, errorKind ast.ErrorP
16981754func generateTupleErrorPropBare (src []byte , expr []byte , exprPos int , returnTypes []string , counter * int , errorKind ast.ErrorPropKind , errorContext string , lambdaParam string , lambdaBody []byte , resolver * codegen.TypeResolver ) []byte {
16991755 var buf bytes.Buffer
17001756
1757+ // Check if there is a named error return parameter in the enclosing function.
1758+ // If so, use that variable name directly (do NOT consume a counter slot).
1759+ // This avoids "no new variables on left side of :=" compile errors.
1760+ namedErrReturn := codegen .InferEnclosingFunctionNamedErrorReturn (src , exprPos )
1761+
17011762 // Generate unique variable names
17021763 var errVar string
1703- if * counter == 1 {
1704- errVar = "err"
1764+ var errDeclOp string
1765+ if namedErrReturn != "" {
1766+ // Use the named error return directly — no new variable needed
1767+ errVar = namedErrReturn
1768+ errDeclOp = " = "
1769+ // Do NOT decrement counter: this location uses named returns, not a fresh counter slot
17051770 } else {
1706- errVar = fmt .Sprintf ("err%d" , * counter - 1 )
1771+ if * counter == 1 {
1772+ errVar = "err"
1773+ } else {
1774+ errVar = fmt .Sprintf ("err%d" , * counter - 1 )
1775+ }
1776+ * counter --
1777+ errDeclOp = " := "
17071778 }
1708- * counter --
17091779
17101780 // Detect return count: 1 = single (error only), 2+ = multi (T, error), -1 = unknown
17111781 // Use the resolver for cross-file type resolution if available
@@ -1719,15 +1789,15 @@ func generateTupleErrorPropBare(src []byte, expr []byte, exprPos int, returnType
17191789 // because the user is explicitly not capturing any non-error return value.
17201790 // If a function returns (T, error) but user wrote a bare `foo()?`, they want to ignore T.
17211791 if returnCount == 1 || returnCount == - 1 {
1722- // Single return or unknown: err := expr
1792+ // Single return or unknown: err := expr (or err = expr for named returns)
17231793 // For bare statements, single-return is safer default (avoids "assignment mismatch" errors)
17241794 buf .WriteString (errVar )
1725- buf .WriteString (" := " )
1795+ buf .WriteString (errDeclOp )
17261796 } else {
1727- // Multi-return (returnCount > 1): _, err := expr
1797+ // Multi-return (returnCount > 1): _, err := expr (or _, err = expr for named returns)
17281798 buf .WriteString ("_, " )
17291799 buf .WriteString (errVar )
1730- buf .WriteString (" := " )
1800+ buf .WriteString (errDeclOp )
17311801 }
17321802 buf .Write (expr )
17331803 buf .WriteByte ('\n' )
0 commit comments