diff --git a/internal/backend/local/backend_local.go b/internal/backend/local/backend_local.go index 774e51357c..11f9dce01a 100644 --- a/internal/backend/local/backend_local.go +++ b/internal/backend/local/backend_local.go @@ -13,6 +13,8 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/zclconf/go-cty/cty" + "maps" + "github.com/hashicorp/terraform/internal/backend/backendrun" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs/configload" @@ -400,9 +402,8 @@ func (b *Local) interactiveCollectVariables(ctx context.Context, existing map[st // variable's value. sort.Strings(needed) // prompt in lexical order ret := make(map[string]backendrun.UnparsedVariableValue, len(vcs)) - for k, v := range existing { - ret[k] = v - } + maps.Copy(ret, existing) // don't use clone here, so we can have a non-nil map + for _, name := range needed { vc := vcs[name] query := fmt.Sprintf("var.%s", name) @@ -467,9 +468,8 @@ func (b *Local) stubUnsetRequiredVariables(existing map[string]backendrun.Unpars // If we get down here then there's at least one variable value to add. ret := make(map[string]backendrun.UnparsedVariableValue, len(vcs)) - for k, v := range existing { - ret[k] = v - } + maps.Copy(ret, existing) // don't use clone here, so we can return a non-nil map + for name, vc := range vcs { if !vc.Required() { continue diff --git a/internal/backend/local/test.go b/internal/backend/local/test.go index e5c1a1fb52..1a8ff7a177 100644 --- a/internal/backend/local/test.go +++ b/internal/backend/local/test.go @@ -8,10 +8,12 @@ import ( "fmt" "log" "path/filepath" - "sort" + "slices" "github.com/zclconf/go-cty/cty" + "maps" + "github.com/hashicorp/terraform/internal/backend/backendrun" "github.com/hashicorp/terraform/internal/command/junit" "github.com/hashicorp/terraform/internal/command/views" @@ -94,28 +96,18 @@ func (runner *TestSuiteRunner) Test() (moduletest.Status, tfdiags.Diagnostics) { runner.View.Abstract(suite) - var files []string - for name := range suite.Files { - files = append(files, name) - } - sort.Strings(files) // execute the files in alphabetical order - // We have two sets of variables that are available to different test files. // Test files in the root directory have access to the GlobalVariables only, // while test files in the test directory have access to the union of // GlobalVariables and GlobalTestVariables. testDirectoryGlobalVariables := make(map[string]backendrun.UnparsedVariableValue) - for name, value := range runner.GlobalVariables { - testDirectoryGlobalVariables[name] = value - } - for name, value := range runner.GlobalTestVariables { - // We're okay to overwrite the global variables in case of name - // collisions, as the test directory variables should take precedence. - testDirectoryGlobalVariables[name] = value - } + maps.Copy(testDirectoryGlobalVariables, runner.GlobalVariables) + // We're okay to overwrite the global variables in case of name + // collisions, as the test directory variables should take precedence. + maps.Copy(testDirectoryGlobalVariables, runner.GlobalTestVariables) suite.Status = moduletest.Pass - for _, name := range files { + for _, name := range slices.Sorted(maps.Keys(suite.Files)) { if runner.Cancelled { return suite.Status, diags } @@ -144,9 +136,7 @@ func (runner *TestSuiteRunner) Test() (moduletest.Status, tfdiags.Diagnostics) { } evalCtx.VariableCaches = hcltest.NewVariableCaches(func(vc *hcltest.VariableCaches) { - for name, value := range currentGlobalVariables { - vc.GlobalVariables[name] = value - } + maps.Copy(vc.GlobalVariables, currentGlobalVariables) vc.FileVariables = file.Config.Variables }) fileRunner := &TestFileRunner{ diff --git a/internal/backend/remote-state/kubernetes/client.go b/internal/backend/remote-state/kubernetes/client.go index 5c44cb6627..4367f86bad 100644 --- a/internal/backend/remote-state/kubernetes/client.go +++ b/internal/backend/remote-state/kubernetes/client.go @@ -26,6 +26,8 @@ import ( "github.com/hashicorp/terraform/internal/states/remote" "github.com/hashicorp/terraform/internal/states/statemgr" + "maps" + coordinationv1 "k8s.io/api/coordination/v1" coordinationclientv1 "k8s.io/client-go/kubernetes/typed/coordination/v1" ) @@ -351,12 +353,7 @@ func (c *RemoteClient) getLabels() map[string]string { tfstateWorkspaceKey: c.workspace, managedByKey: "terraform", } - - if len(c.labels) != 0 { - for k, v := range c.labels { - l[k] = v - } - } + maps.Copy(l, c.labels) return l } diff --git a/internal/command/cliconfig/cliconfig.go b/internal/command/cliconfig/cliconfig.go index 43c4b5632e..e162fe44f9 100644 --- a/internal/command/cliconfig/cliconfig.go +++ b/internal/command/cliconfig/cliconfig.go @@ -17,6 +17,7 @@ import ( "io/fs" "io/ioutil" "log" + "maps" "os" "path/filepath" "strings" @@ -341,18 +342,14 @@ func (c *Config) Merge(c2 *Config) *Config { var result Config result.Providers = make(map[string]string) result.Provisioners = make(map[string]string) - for k, v := range c.Providers { - result.Providers[k] = v - } + maps.Copy(result.Providers, c.Providers) + maps.Copy(result.Provisioners, c.Provisioners) for k, v := range c2.Providers { if v1, ok := c.Providers[k]; ok { log.Printf("[INFO] Local %s provider configuration '%s' overrides '%s'", k, v, v1) } result.Providers[k] = v } - for k, v := range c.Provisioners { - result.Provisioners[k] = v - } for k, v := range c2.Provisioners { if v1, ok := c.Provisioners[k]; ok { log.Printf("[INFO] Local %s provisioner configuration '%s' overrides '%s'", k, v, v1) @@ -375,35 +372,23 @@ func (c *Config) Merge(c2 *Config) *Config { if (len(c.Hosts) + len(c2.Hosts)) > 0 { result.Hosts = make(map[string]*ConfigHost) - for name, host := range c.Hosts { - result.Hosts[name] = host - } - for name, host := range c2.Hosts { - result.Hosts[name] = host - } + maps.Copy(result.Hosts, c.Hosts) + maps.Copy(result.Hosts, c2.Hosts) } if (len(c.Credentials) + len(c2.Credentials)) > 0 { result.Credentials = make(map[string]map[string]interface{}) - for host, creds := range c.Credentials { - result.Credentials[host] = creds - } - for host, creds := range c2.Credentials { - // We just clobber an entry from the other file right now. Will - // improve on this later using the more-robust merging behavior - // built in to HCL2. - result.Credentials[host] = creds - } + maps.Copy(result.Credentials, c.Credentials) + // We just clobber an entry from the other file right now. Will + // improve on this later using the more-robust merging behavior + // built in to HCL2. + maps.Copy(result.Credentials, c2.Credentials) } if (len(c.CredentialsHelpers) + len(c2.CredentialsHelpers)) > 0 { result.CredentialsHelpers = make(map[string]*ConfigCredentialsHelper) - for name, helper := range c.CredentialsHelpers { - result.CredentialsHelpers[name] = helper - } - for name, helper := range c2.CredentialsHelpers { - result.CredentialsHelpers[name] = helper - } + maps.Copy(result.CredentialsHelpers, c.CredentialsHelpers) + maps.Copy(result.CredentialsHelpers, c2.CredentialsHelpers) } if (len(c.ProviderInstallation) + len(c2.ProviderInstallation)) > 0 { diff --git a/internal/command/format/diagnostic.go b/internal/command/format/diagnostic.go index dc7ecd0dda..5915f26bdf 100644 --- a/internal/command/format/diagnostic.go +++ b/internal/command/format/diagnostic.go @@ -8,6 +8,7 @@ import ( "bytes" "fmt" "iter" + "slices" "sort" "strings" @@ -300,8 +301,7 @@ func (f *snippetFormatter) write() { // This is particularly useful for expressions that get evaluated // multiple times with different values, such as blocks using // "count" and "for_each", or within "for" expressions. - values := make([]viewsjson.DiagnosticExpressionValue, len(snippet.Values)) - copy(values, snippet.Values) + values := slices.Clone(snippet.Values) sort.Slice(values, func(i, j int) bool { return values[i].Traversal < values[j].Traversal }) diff --git a/internal/command/jsonformat/state.go b/internal/command/jsonformat/state.go index de3c83f491..04db2b24e5 100644 --- a/internal/command/jsonformat/state.go +++ b/internal/command/jsonformat/state.go @@ -4,7 +4,8 @@ package jsonformat import ( - "sort" + "maps" + "slices" ctyjson "github.com/zclconf/go-cty/cty/json" @@ -88,13 +89,7 @@ func (state State) renderHumanStateOutputs(renderer Renderer, opts computed.Rend if len(state.RootModuleOutputs) > 0 { renderer.Streams.Printf("\n\nOutputs:\n\n") - var keys []string - for key := range state.RootModuleOutputs { - keys = append(keys, key) - } - sort.Strings(keys) - - for _, key := range keys { + for _, key := range slices.Sorted(maps.Keys(state.RootModuleOutputs)) { output := state.RootModuleOutputs[key] change := structured.FromJsonOutput(output) ctype, err := ctyjson.UnmarshalType(output.Type) diff --git a/internal/command/jsonstate/state.go b/internal/command/jsonstate/state.go index 38f9179bf0..52e93dfa58 100644 --- a/internal/command/jsonstate/state.go +++ b/internal/command/jsonstate/state.go @@ -6,6 +6,8 @@ package jsonstate import ( "encoding/json" "fmt" + "maps" + "slices" "sort" "github.com/zclconf/go-cty/cty" @@ -477,13 +479,7 @@ func marshalResources(resources map[string]*states.Resource, module addrs.Module ret = append(ret, current) } - var sortedDeposedKeys []string - for k := range ri.Deposed { - sortedDeposedKeys = append(sortedDeposedKeys, string(k)) - } - sort.Strings(sortedDeposedKeys) - - for _, deposedKey := range sortedDeposedKeys { + for _, deposedKey := range slices.Sorted(maps.Keys(ri.Deposed)) { rios := ri.Deposed[states.DeposedKey(deposedKey)] // copy the base fields from the current instance @@ -531,7 +527,7 @@ func marshalResources(resources map[string]*states.Resource, module addrs.Module if riObj.Status == states.ObjectTainted { deposed.Tainted = true } - deposed.DeposedKey = deposedKey + deposed.DeposedKey = string(deposedKey) ret = append(ret, deposed) } } diff --git a/internal/command/junit/junit.go b/internal/command/junit/junit.go index 34051ddfa2..c62c51d581 100644 --- a/internal/command/junit/junit.go +++ b/internal/command/junit/junit.go @@ -6,6 +6,7 @@ import ( "bytes" "encoding/xml" "fmt" + "maps" "os" "slices" "strconv" @@ -155,8 +156,9 @@ func junitXMLTestReport(suite *moduletest.Suite, suiteRunnerStopped bool, source enc.EncodeToken(xml.StartElement{Name: suitesName}) - sortedFiles := suiteFilesAsSortedList(suite.Files) // to ensure consistent ordering in XML - for _, file := range sortedFiles { + // Sort the file names to ensure consistent ordering in XML + for _, name := range slices.Sorted(maps.Keys(suite.Files)) { + file := suite.Files[name] // Each test file is modelled as a "test suite". // First we'll count the number of tests and number of failures/errors @@ -327,22 +329,6 @@ func skipDetails(runIndex int, file *moduletest.File, suiteStopped bool) *withMe return &withMessage{} } -func suiteFilesAsSortedList(files map[string]*moduletest.File) []*moduletest.File { - fileNames := make([]string, len(files)) - i := 0 - for k := range files { - fileNames[i] = k - i++ - } - slices.Sort(fileNames) - - sortedFiles := make([]*moduletest.File, len(files)) - for i, name := range fileNames { - sortedFiles[i] = files[name] - } - return sortedFiles -} - func getDiagString(diags tfdiags.Diagnostics, sources map[string][]byte) string { var diagsStr strings.Builder for _, d := range diags { diff --git a/internal/command/junit/junit_internal_test.go b/internal/command/junit/junit_internal_test.go index 41ab23f82f..19cffe4959 100644 --- a/internal/command/junit/junit_internal_test.go +++ b/internal/command/junit/junit_internal_test.go @@ -7,8 +7,6 @@ import ( "fmt" "os" "testing" - - "github.com/hashicorp/terraform/internal/moduletest" ) func Test_TestJUnitXMLFile_save(t *testing.T) { @@ -66,88 +64,3 @@ func Test_TestJUnitXMLFile_save(t *testing.T) { }) } } - -func Test_suiteFilesAsSortedList(t *testing.T) { - cases := map[string]struct { - Suite *moduletest.Suite - ExpectedNames map[int]string - }{ - "no test files": { - Suite: &moduletest.Suite{}, - }, - "3 test files ordered in map": { - Suite: &moduletest.Suite{ - Status: moduletest.Skip, - Files: map[string]*moduletest.File{ - "test_file_1.tftest.hcl": { - Name: "test_file_1.tftest.hcl", - Status: moduletest.Skip, - Runs: []*moduletest.Run{}, - }, - "test_file_2.tftest.hcl": { - Name: "test_file_2.tftest.hcl", - Status: moduletest.Skip, - Runs: []*moduletest.Run{}, - }, - "test_file_3.tftest.hcl": { - Name: "test_file_3.tftest.hcl", - Status: moduletest.Skip, - Runs: []*moduletest.Run{}, - }, - }, - }, - ExpectedNames: map[int]string{ - 0: "test_file_1.tftest.hcl", - 1: "test_file_2.tftest.hcl", - 2: "test_file_3.tftest.hcl", - }, - }, - "3 test files unordered in map": { - Suite: &moduletest.Suite{ - Status: moduletest.Skip, - Files: map[string]*moduletest.File{ - "test_file_3.tftest.hcl": { - Name: "test_file_3.tftest.hcl", - Status: moduletest.Skip, - Runs: []*moduletest.Run{}, - }, - "test_file_1.tftest.hcl": { - Name: "test_file_1.tftest.hcl", - Status: moduletest.Skip, - Runs: []*moduletest.Run{}, - }, - "test_file_2.tftest.hcl": { - Name: "test_file_2.tftest.hcl", - Status: moduletest.Skip, - Runs: []*moduletest.Run{}, - }, - }, - }, - ExpectedNames: map[int]string{ - 0: "test_file_1.tftest.hcl", - 1: "test_file_2.tftest.hcl", - 2: "test_file_3.tftest.hcl", - }, - }, - } - - for tn, tc := range cases { - t.Run(tn, func(t *testing.T) { - list := suiteFilesAsSortedList(tc.Suite.Files) - - if len(tc.ExpectedNames) != len(tc.Suite.Files) { - t.Fatalf("expected there to be %d items, got %d", len(tc.ExpectedNames), len(tc.Suite.Files)) - } - - if len(tc.ExpectedNames) == 0 { - return - } - - for k, v := range tc.ExpectedNames { - if list[k].Name != v { - t.Fatalf("expected element %d in sorted list to be named %s, got %s", k, v, list[k].Name) - } - } - }) - } -} diff --git a/internal/command/meta.go b/internal/command/meta.go index 5fc63223ff..f7b7313409 100644 --- a/internal/command/meta.go +++ b/internal/command/meta.go @@ -11,6 +11,7 @@ import ( "fmt" "io/ioutil" "log" + "maps" "os" "path/filepath" "strconv" @@ -293,9 +294,7 @@ func (m *Meta) StateOutPath() string { // Colorize returns the colorization structure for a command. func (m *Meta) Colorize() *colorstring.Colorize { colors := make(map[string]string) - for k, v := range colorstring.DefaultColors { - colors[k] = v - } + maps.Copy(colors, colorstring.DefaultColors) colors["purple"] = "38;5;57" return &colorstring.Colorize{ diff --git a/internal/configs/config.go b/internal/configs/config.go index eada09d56a..ce07d965c0 100644 --- a/internal/configs/config.go +++ b/internal/configs/config.go @@ -6,7 +6,10 @@ package configs import ( "fmt" "log" + "maps" + "slices" "sort" + "strings" version "github.com/hashicorp/go-version" "github.com/hashicorp/hcl/v2" @@ -135,13 +138,8 @@ func (c *Config) Depth() int { func (c *Config) DeepEach(cb func(c *Config)) { cb(c) - names := make([]string, 0, len(c.Children)) - for name := range c.Children { - names = append(names, name) - } - - for _, name := range names { - c.Children[name].DeepEach(cb) + for _, ch := range c.Children { + ch.DeepEach(cb) } } @@ -826,12 +824,8 @@ func (c *Config) ProviderTypes() []addrs.Provider { // Ignore diagnostics here because they relate to version constraints reqs, _ := c.ProviderRequirements() - ret := make([]addrs.Provider, 0, len(reqs)) - for k := range reqs { - ret = append(ret, k) - } - sort.Slice(ret, func(i, j int) bool { - return ret[i].String() < ret[j].String() + ret := slices.SortedFunc(maps.Keys(reqs), func(i, j addrs.Provider) int { + return strings.Compare(i.String(), j.String()) }) return ret } diff --git a/internal/configs/config_build.go b/internal/configs/config_build.go index d0246f27ef..3f3378a2d0 100644 --- a/internal/configs/config_build.go +++ b/internal/configs/config_build.go @@ -5,8 +5,9 @@ package configs import ( "fmt" + "maps" "path" - "sort" + "slices" "strings" version "github.com/hashicorp/go-version" @@ -150,17 +151,10 @@ func buildChildModules(parent *Config, walker ModuleWalker) (map[string]*Config, // We'll sort the calls by their local names so that they'll appear in a // predictable order in any logging that's produced during the walk. - callNames := make([]string, 0, len(calls)) - for k := range calls { - callNames = append(callNames, k) - } - sort.Strings(callNames) - - for _, callName := range callNames { + for _, callName := range slices.Sorted(maps.Keys(calls)) { call := calls[callName] - path := make([]string, len(parent.Path)+1) - copy(path, parent.Path) - path[len(path)-1] = call.Name + path := slices.Clone(parent.Path) + path = append(path, call.Name) req := ModuleRequest{ Name: call.Name, diff --git a/internal/configs/configload/loader_snapshot.go b/internal/configs/configload/loader_snapshot.go index 1d1c85c129..d8260cddd7 100644 --- a/internal/configs/configload/loader_snapshot.go +++ b/internal/configs/configload/loader_snapshot.go @@ -6,9 +6,10 @@ package configload import ( "fmt" "io" + "maps" "os" "path/filepath" - "sort" + "slices" "time" version "github.com/hashicorp/go-version" @@ -257,11 +258,7 @@ func (fs snapshotFS) Open(name string) (afero.File, error) { modDir := filepath.Clean(candidate.Dir) if modDir == directDir { // We've matched the module directory itself - filenames := make([]string, 0, len(candidate.Files)) - for n := range candidate.Files { - filenames = append(filenames, n) - } - sort.Strings(filenames) + filenames := slices.Sorted(maps.Keys(candidate.Files)) return &snapshotDir{ filenames: filenames, }, nil diff --git a/internal/dag/dot.go b/internal/dag/dot.go index 95129edda0..af9d5c8680 100644 --- a/internal/dag/dot.go +++ b/internal/dag/dot.go @@ -6,6 +6,8 @@ package dag import ( "bytes" "fmt" + "maps" + "slices" "sort" "strings" ) @@ -97,13 +99,8 @@ func (v *marshalVertex) dot(g *marshalGraph, opts *DotOpts) []byte { return []byte{} } - newAttrs := make(map[string]string) - for k, v := range attrs { - newAttrs[k] = v - } - for k, v := range node.Attrs { - newAttrs[k] = v - } + newAttrs := maps.Clone(attrs) + maps.Copy(newAttrs, node.Attrs) name = node.Name attrs = newAttrs @@ -218,13 +215,7 @@ func (g *marshalGraph) writeBody(opts *DotOpts, w *indentWriter) { } // sort these again to match the old output - dotEdgesList := make([]string, 0, len(dotEdges)) - for _, v := range dotEdges { - dotEdgesList = append(dotEdgesList, v) - } - sort.Strings(dotEdgesList) - - for _, e := range dotEdgesList { + for _, e := range slices.Sorted(maps.Values(dotEdges)) { w.WriteString(e + "\n") } diff --git a/internal/dag/set.go b/internal/dag/set.go index 51358eba41..145cf31327 100644 --- a/internal/dag/set.go +++ b/internal/dag/set.go @@ -3,6 +3,11 @@ package dag +import ( + "iter" + "maps" +) + // Set is a set data structure. type Set map[interface{}]interface{} @@ -92,25 +97,18 @@ func (s Set) Len() int { return len(s) } -// List returns the list of set elements. -func (s Set) List() []interface{} { - if s == nil { - return nil - } - - r := make([]interface{}, 0, len(s)) - for _, v := range s { - r = append(r, v) +// List returns the sequence of set elements. +func (s Set) List() iter.Seq[any] { + return func(yield func(any) bool) { + for _, v := range s { + if !yield(v) { + return + } + } } - - return r } // Copy returns a shallow copy of the set. func (s Set) Copy() Set { - c := make(Set, len(s)) - for k, v := range s { - c[k] = v - } - return c + return maps.Clone(s) } diff --git a/internal/depsfile/locks.go b/internal/depsfile/locks.go index 4e56abbe13..9b3bcffc10 100644 --- a/internal/depsfile/locks.go +++ b/internal/depsfile/locks.go @@ -5,8 +5,11 @@ package depsfile import ( "fmt" + "slices" "sort" + "maps" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/getproviders/providerreqs" ) @@ -69,11 +72,7 @@ func (l *Locks) Provider(addr addrs.Provider) *ProviderLock { func (l *Locks) AllProviders() map[addrs.Provider]*ProviderLock { // We return a copy of our internal map so that future calls to // SetProvider won't modify the map we're returning, or vice-versa. - ret := make(map[addrs.Provider]*ProviderLock, len(l.providers)) - for k, v := range l.providers { - ret[k] = v - } - return ret + return maps.Clone(l.providers) } // SetProvider creates a new lock or replaces the existing lock for the given @@ -315,11 +314,7 @@ func (l *Locks) Empty() bool { func (l *Locks) DeepCopy() *Locks { ret := NewLocks() for addr, lock := range l.providers { - var hashes []providerreqs.Hash - if len(lock.hashes) > 0 { - hashes = make([]providerreqs.Hash, len(lock.hashes)) - copy(hashes, lock.hashes) - } + hashes := slices.Clone(lock.hashes) ret.SetProvider(addr, lock.version, lock.versionConstraints, hashes) } return ret diff --git a/internal/depsfile/locks_file.go b/internal/depsfile/locks_file.go index 6e7f21e180..a6b3962103 100644 --- a/internal/depsfile/locks_file.go +++ b/internal/depsfile/locks_file.go @@ -5,6 +5,8 @@ package depsfile import ( "fmt" + "maps" + "slices" "sort" "github.com/hashicorp/hcl/v2" @@ -138,10 +140,7 @@ func SaveLocksToBytes(locks *Locks) ([]byte, tfdiags.Diagnostics) { }, }) - providers := make([]addrs.Provider, 0, len(locks.providers)) - for provider := range locks.providers { - providers = append(providers, provider) - } + providers := slices.Collect(maps.Keys(locks.providers)) sort.Slice(providers, func(i, j int) bool { return providers[i].LessThan(providers[j]) }) diff --git a/internal/genconfig/generate_config.go b/internal/genconfig/generate_config.go index 87507048b9..1faf9939e7 100644 --- a/internal/genconfig/generate_config.go +++ b/internal/genconfig/generate_config.go @@ -6,7 +6,8 @@ package genconfig import ( "encoding/json" "fmt" - "sort" + "maps" + "slices" "strings" "github.com/hashicorp/hcl/v2" @@ -72,14 +73,7 @@ func writeConfigAttributes(addr addrs.AbsResourceInstance, buf *strings.Builder, } // Get a list of sorted attribute names so the output will be consistent between runs. - keys := make([]string, 0, len(attrs)) - for k := range attrs { - keys = append(keys, k) - } - sort.Strings(keys) - - for i := range keys { - name := keys[i] + for _, name := range slices.Sorted(maps.Keys(attrs)) { attrS := attrs[name] if attrS.NestedType != nil { diags = diags.Append(writeConfigNestedTypeAttribute(addr, buf, name, attrS, indent)) @@ -124,15 +118,8 @@ func writeConfigAttributesFromExisting(addr addrs.AbsResourceInstance, buf *stri return diags } - // Get a list of sorted attribute names so the output will be consistent between runs. - keys := make([]string, 0, len(attrs)) - for k := range attrs { - keys = append(keys, k) - } - sort.Strings(keys) - - for i := range keys { - name := keys[i] + // Sort attribute names so the output will be consistent between runs. + for _, name := range slices.Sorted(maps.Keys(attrs)) { attrS := attrs[name] if attrS.NestedType != nil { writeConfigNestedTypeAttributeFromExisting(addr, buf, name, attrS, stateVal, indent) @@ -233,14 +220,7 @@ func writeConfigBlocks(addr addrs.AbsResourceInstance, buf *strings.Builder, blo } // Get a list of sorted block names so the output will be consistent between runs. - names := make([]string, 0, len(blocks)) - for k := range blocks { - names = append(names, k) - } - sort.Strings(names) - - for i := range names { - name := names[i] + for _, name := range slices.Sorted(maps.Keys(blocks)) { blockS := blocks[name] diags = diags.Append(writeConfigNestedBlock(addr, buf, name, blockS, indent)) } @@ -329,14 +309,8 @@ func writeConfigBlocksFromExisting(addr addrs.AbsResourceInstance, buf *strings. return diags } - // Get a list of sorted block names so the output will be consistent between runs. - names := make([]string, 0, len(blocks)) - for k := range blocks { - names = append(names, k) - } - sort.Strings(names) - - for _, name := range names { + // Sort block names so the output will be consistent between runs. + for _, name := range slices.Sorted(maps.Keys(blocks)) { blockS := blocks[name] // This shouldn't happen in real usage; state always has all values (set // to null as needed), but it protects against panics in tests (and any @@ -436,15 +410,10 @@ func writeConfigNestedTypeAttributeFromExisting(addr addrs.AbsResourceInstance, } vals := attr.AsValueMap() - keys := make([]string, 0, len(vals)) - for key := range vals { - keys = append(keys, key) - } - sort.Strings(keys) buf.WriteString(strings.Repeat(" ", indent)) buf.WriteString(fmt.Sprintf("%s = {\n", name)) - for _, key := range keys { + for _, key := range slices.Sorted(maps.Keys(vals)) { buf.WriteString(strings.Repeat(" ", indent+2)) buf.WriteString(fmt.Sprintf("%s = {", hclEscapeString(key))) @@ -513,12 +482,7 @@ func writeConfigNestedBlockFromExisting(addr addrs.AbsResourceInstance, buf *str } vals := stateVal.AsValueMap() - keys := make([]string, 0, len(vals)) - for key := range vals { - keys = append(keys, key) - } - sort.Strings(keys) - for _, key := range keys { + for _, key := range slices.Sorted(maps.Keys(vals)) { buf.WriteString(strings.Repeat(" ", indent)) buf.WriteString(fmt.Sprintf("%s %q {", name, key)) // This entire map element is marked diff --git a/internal/lang/eval.go b/internal/lang/eval.go index 977587633a..e654e2e123 100644 --- a/internal/lang/eval.go +++ b/internal/lang/eval.go @@ -15,6 +15,8 @@ import ( "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" + "maps" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/instances" @@ -442,10 +444,10 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl // traversal, but we also expose them under "resource" as an escaping // technique if we add a reserved name in a future language edition which // conflicts with someone's existing provider. - for k, v := range buildResourceObjects(managedResources) { - vals[k] = v - } - vals["resource"] = cty.ObjectVal(buildResourceObjects(managedResources)) + builtManagedResources := buildResourceObjects(managedResources) + maps.Copy(vals, builtManagedResources) + + vals["resource"] = cty.ObjectVal(builtManagedResources) vals["ephemeral"] = cty.ObjectVal(buildResourceObjects(ephemeralResources)) vals["data"] = cty.ObjectVal(buildResourceObjects(dataResources)) vals["module"] = cty.ObjectVal(wholeModules) diff --git a/internal/moduleref/resolver.go b/internal/moduleref/resolver.go index d66ce6d566..94d80c4023 100644 --- a/internal/moduleref/resolver.go +++ b/internal/moduleref/resolver.go @@ -4,6 +4,7 @@ package moduleref import ( + "maps" "strings" "github.com/hashicorp/go-version" @@ -23,10 +24,7 @@ type Resolver struct { func NewResolver(internalManifest modsdir.Manifest) *Resolver { // Since maps are pointers, create a copy of the internal manifest to // prevent introducing side effects to the original - internalManifestCopy := make(modsdir.Manifest, len(internalManifest)) - for k, v := range internalManifest { - internalManifestCopy[k] = v - } + internalManifestCopy := maps.Clone(internalManifest) // Remove the root module entry from the internal manifest as it is // never directly referenced. diff --git a/internal/moduletest/graph/eval_context.go b/internal/moduletest/graph/eval_context.go index 8ad8e1e560..f925186a0a 100644 --- a/internal/moduletest/graph/eval_context.go +++ b/internal/moduletest/graph/eval_context.go @@ -14,6 +14,8 @@ import ( "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" + "maps" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/command/views" "github.com/hashicorp/terraform/internal/configs" @@ -313,9 +315,7 @@ func (ec *EvalContext) GetOutputs() map[addrs.Run]cty.Value { ec.outputsLock.Lock() defer ec.outputsLock.Unlock() outputCopy := make(map[addrs.Run]cty.Value, len(ec.runOutputs)) - for k, v := range ec.runOutputs { - outputCopy[k] = v - } + maps.Copy(outputCopy, ec.runOutputs) // don't use clone here, so we can return a non-nil map return outputCopy } diff --git a/internal/plans/planfile/config_snapshot.go b/internal/plans/planfile/config_snapshot.go index 7dda3bc5f5..5f4256a627 100644 --- a/internal/plans/planfile/config_snapshot.go +++ b/internal/plans/planfile/config_snapshot.go @@ -8,8 +8,9 @@ import ( "encoding/json" "fmt" "io" + "maps" "path" - "sort" + "slices" "strings" "time" @@ -158,15 +159,10 @@ func writeConfigSnapshot(snap *configload.Snapshot, z *zip.Writer) error { // need to be user-actionable. var manifest configSnapshotModuleManifest - keys := make([]string, 0, len(snap.Modules)) - for k := range snap.Modules { - keys = append(keys, k) - } - sort.Strings(keys) // We'll re-use this fileheader for each Create we do below. - for _, k := range keys { + for _, k := range slices.Sorted(maps.Keys(snap.Modules)) { snapMod := snap.Modules[k] record := configSnapshotModuleRecord{ Dir: snapMod.Dir, diff --git a/internal/plans/planfile/tfplan.go b/internal/plans/planfile/tfplan.go index f8c88404fa..ea37a8a1da 100644 --- a/internal/plans/planfile/tfplan.go +++ b/internal/plans/planfile/tfplan.go @@ -6,6 +6,7 @@ package planfile import ( "fmt" "io" + "slices" "time" "github.com/zclconf/go-cty/cty" @@ -598,10 +599,7 @@ func writeTfplan(plan *plans.Plan, w io.Writer) error { rawPlan.Variables[name] = valueToTfplan(val) } if plan.ApplyTimeVariables.Len() != 0 { - rawPlan.ApplyTimeVariables = make([]string, 0, plan.ApplyTimeVariables.Len()) - for name := range plan.ApplyTimeVariables.All() { - rawPlan.ApplyTimeVariables = append(rawPlan.ApplyTimeVariables, name) - } + rawPlan.ApplyTimeVariables = slices.Collect(plan.ApplyTimeVariables.All()) } for _, hash := range plan.ProviderFunctionResults { diff --git a/internal/states/state_deepcopy.go b/internal/states/state_deepcopy.go index aecc70415e..1a0c102362 100644 --- a/internal/states/state_deepcopy.go +++ b/internal/states/state_deepcopy.go @@ -4,8 +4,10 @@ package states import ( + "maps" + "slices" + "github.com/hashicorp/terraform/internal/addrs" - "github.com/zclconf/go-cty/cty" ) // Taking deep copies of states is an important operation because state is @@ -128,45 +130,15 @@ func (os *ResourceInstanceObjectSrc) DeepCopy() *ResourceInstanceObjectSrc { return nil } - var attrsFlat map[string]string - if os.AttrsFlat != nil { - attrsFlat = make(map[string]string, len(os.AttrsFlat)) - for k, v := range os.AttrsFlat { - attrsFlat[k] = v - } - } - - var attrsJSON []byte - if os.AttrsJSON != nil { - attrsJSON = make([]byte, len(os.AttrsJSON)) - copy(attrsJSON, os.AttrsJSON) - } - - var identityJSON []byte - if os.IdentityJSON != nil { - identityJSON = make([]byte, len(os.IdentityJSON)) - copy(identityJSON, os.IdentityJSON) - } - - var sensitiveAttrPaths []cty.Path - if os.AttrSensitivePaths != nil { - sensitiveAttrPaths = make([]cty.Path, len(os.AttrSensitivePaths)) - copy(sensitiveAttrPaths, os.AttrSensitivePaths) - } - - var private []byte - if os.Private != nil { - private = make([]byte, len(os.Private)) - copy(private, os.Private) - } + attrsFlat := maps.Clone(os.AttrsFlat) + attrsJSON := slices.Clone(os.AttrsJSON) + identityJSON := slices.Clone(os.IdentityJSON) + sensitiveAttrPaths := slices.Clone(os.AttrSensitivePaths) + private := slices.Clone(os.Private) // Some addrs.Referencable implementations are technically mutable, but // we treat them as immutable by convention and so we don't deep-copy here. - var dependencies []addrs.ConfigResource - if os.Dependencies != nil { - dependencies = make([]addrs.ConfigResource, len(os.Dependencies)) - copy(dependencies, os.Dependencies) - } + dependencies := slices.Clone(os.Dependencies) return &ResourceInstanceObjectSrc{ Status: os.Status, @@ -197,19 +169,11 @@ func (o *ResourceInstanceObject) DeepCopy() *ResourceInstanceObject { return nil } - var private []byte - if o.Private != nil { - private = make([]byte, len(o.Private)) - copy(private, o.Private) - } + private := slices.Clone(o.Private) // Some addrs.Referenceable implementations are technically mutable, but // we treat them as immutable by convention and so we don't deep-copy here. - var dependencies []addrs.ConfigResource - if o.Dependencies != nil { - dependencies = make([]addrs.ConfigResource, len(o.Dependencies)) - copy(dependencies, o.Dependencies) - } + dependencies := slices.Clone(o.Dependencies) return &ResourceInstanceObject{ Value: o.Value, diff --git a/internal/states/statefile/version3_upgrade.go b/internal/states/statefile/version3_upgrade.go index d84a5ecaae..4f36058684 100644 --- a/internal/states/statefile/version3_upgrade.go +++ b/internal/states/statefile/version3_upgrade.go @@ -13,6 +13,8 @@ import ( "github.com/zclconf/go-cty/cty" ctyjson "github.com/zclconf/go-cty/cty/json" + "maps" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/states" @@ -315,13 +317,7 @@ func upgradeInstanceObjectV3ToV4(rsOld *resourceStateV2, isOld *instanceStateV2, } } - var attributes map[string]string - if isOld.Attributes != nil { - attributes = make(map[string]string, len(isOld.Attributes)) - for k, v := range isOld.Attributes { - attributes[k] = v - } - } + attributes := maps.Clone(isOld.Attributes) if isOld.ID != "" { // As a special case, if we don't already have an "id" attribute and // yet there's a non-empty first-class ID on the old object then we'll diff --git a/internal/terraform/transform_ephemeral_resource_close.go b/internal/terraform/transform_ephemeral_resource_close.go index d9acc20162..adec22343d 100644 --- a/internal/terraform/transform_ephemeral_resource_close.go +++ b/internal/terraform/transform_ephemeral_resource_close.go @@ -76,7 +76,7 @@ func (t *ephemeralResourceCloseTransformer) Transform(g *Graph) error { return len(up) == 0 }) - for _, last := range lastReferences.List() { + for last := range lastReferences.List() { g.Connect(dag.BasicEdge(closeNode, last)) } }