internal/core/export: dedup let clauses for merged structs

Let deduping already existed, but it could miss cases where
an identical let was added to the same scope, but originating
from different conjuncts, where the let originated from the
same struct.

Fixes #593

Change-Id: I47c806096ba885421005dfb297ae700c1c734040
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7723
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
diff --git a/internal/core/export/adt.go b/internal/core/export/adt.go
index 17d591e..58da232 100644
--- a/internal/core/export/adt.go
+++ b/internal/core/export/adt.go
@@ -166,12 +166,30 @@
 				f.let = map[adt.Expr]*ast.LetClause{}
 			}
 			label := e.uniqueLetIdent(x.Label, x.X)
-			let = &ast.LetClause{
-				Ident: e.ident(label),
-				Expr:  e.expr(x.X),
+
+			name := label.IdentString(e.ctx)
+
+			// A let may be added multiple times to the same scope as a result
+			// of how merging works. If that happens here it must be one
+			// originating from the same expression, and it is safe to drop.
+			for _, elt := range f.scope.Elts {
+				if a, ok := elt.(*ast.LetClause); ok {
+					if a.Ident.Name == name {
+						let = a
+						break
+					}
+				}
 			}
+
+			if let == nil {
+				let = &ast.LetClause{
+					Ident: e.ident(label),
+					Expr:  e.expr(x.X),
+				}
+				f.scope.Elts = append(f.scope.Elts, let)
+			}
+
 			f.let[x.X] = let
-			f.scope.Elts = append(f.scope.Elts, let)
 		}
 		ident := ast.NewIdent(let.Ident.Name)
 		ident.Node = let
diff --git a/internal/core/export/testdata/let.txtar b/internal/core/export/testdata/let.txtar
index ced992d..641b124 100644
--- a/internal/core/export/testdata/let.txtar
+++ b/internal/core/export/testdata/let.txtar
@@ -16,8 +16,38 @@
 // Issue #590
 let Y = x
 y: Y
+-- issue593.cue --
+cfgs: [ for crd in ["one", "two"] {
+        metadata: {
+                name: crd
+        }
+}]
+for cfg in cfgs {
+        let filepath = "kind-\(cfg.name)"
+        files: {
+                "\(filepath)": {
+                        patches: cfg
+                }
+        }
+}
+
 -- out/definition --
 
+{
+	cfgs: [ for crd in ["one", "two"] {
+		metadata: {
+			name: crd
+		}
+	}]
+	for cfg in cfgs {
+		let filepath = "kind-\(cfg.name)"
+		files: {
+			"\(filepath)": {
+				patches: cfg
+			}
+		}
+	}
+}
 let X = 1 + 1
 #Foo: X
 x:    "foo"
@@ -29,26 +59,110 @@
 [#Foo]
 [x]
 [y]
+[cfgs]
+[cfgs 0]
+[cfgs 0 metadata]
+[cfgs 0 metadata name]
+[cfgs 1]
+[cfgs 1 metadata]
+[cfgs 1 metadata name]
+[files]
 -- out/value --
 == Simplified
 {
 	x: "foo"
+	cfgs: [{
+		metadata: {
+			name: "one"
+		}
+	}, {
+		metadata: {
+			name: "two"
+		}
+	}]
+	let filepath = "kind-\(cfg.name)"
+	files: {
+		"\(filepath)": {
+			patches: cfg
+		}
+	} & {
+		"\(filepath)": {
+			patches: cfg
+		}
+	}
 	y: "foo"
 }
 == Raw
 {
 	#Foo: 2
 	x:    "foo"
-	y:    "foo"
+	cfgs: [{
+		metadata: {
+			name: "one"
+		}
+	}, {
+		metadata: {
+			name: "two"
+		}
+	}]
+	let filepath = "kind-\(cfg.name)"
+	files: {
+		"\(filepath)": {
+			patches: cfg
+		}
+	} & {
+		"\(filepath)": {
+			patches: cfg
+		}
+	}
+	y: "foo"
 }
 == Final
 {
 	x: "foo"
+	cfgs: [{
+		metadata: {
+			name: "one"
+		}
+	}, {
+		metadata: {
+			name: "two"
+		}
+	}]
+	let filepath = "kind-\(cfg.name)"
+	files: {
+		"\(filepath)": {
+			patches: cfg
+		}
+	} & {
+		"\(filepath)": {
+			patches: cfg
+		}
+	}
 	y: "foo"
 }
 == All
 {
 	#Foo: 2
 	x:    "foo"
-	y:    "foo"
+	cfgs: [{
+		metadata: {
+			name: "one"
+		}
+	}, {
+		metadata: {
+			name: "two"
+		}
+	}]
+	let filepath = "kind-\(cfg.name)"
+	files: {
+		"\(filepath)": {
+			patches: cfg
+		}
+	} & {
+		"\(filepath)": {
+			patches: cfg
+		}
+	}
+	y: "foo"
 }