Repository: opentofu/opentofu
File: internal/getproviders/types.go (Go)

Code before:

// VersionConstraints represents a set of version constraints, which can
// define the membership of a VersionSet by exclusion.
type VersionConstraints = constraints.IntersectionSpec

// Warnings represents a list of warnings returned by a Registry source.
type Warnings = []string

// Requirements gathers together requirements for many different providers
// into a single data structure, as a convenient way to represent the full
// set of requirements for a particular configuration or state or both.
//
// If an entry in a Requirements has a zero-length VersionConstraints then
// that indicates that the provider is required but that any version is
// acceptable. That's different than a provider being absent from the map
// altogether, which means that it is not required at all.
type Requirements map[addrs.Provider]VersionConstraints

// ProvidersQualification is storing the implicit/explicit reference qualification of the providers.
// This is necessary to be able to warn the user when the resources are referencing a provider that
// is not specifically defined in a required_providers block. When the implicitly referenced
// provider is tried to be downloaded without a specific provider requirement, it will be tried
// from the default namespace (hashicorp), failing to download it when it does not exist in the default namespace.
// Therefore, we want to let the user know what resources are generating this situation.
type ProvidersQualification struct {
	Implicit map[addrs.Provider][]hcl.Range
	Explicit map[addrs.Provider]struct{}
}

// AddImplicitProvider saves an addrs.Provider with the place in the configuration where this is generated from.
func (pq *ProvidersQualification) AddImplicitProvider(provider addrs.Provider, ref hcl.Range) {
	if pq.Implicit == nil {
		pq.Implicit = map[addrs.Provider][]hcl.Range{}
	}
	refs := pq.Implicit[provider]
	refs = append(refs, ref)
	pq.Implicit[provider] = refs
}

// AddExplicitProvider saves an addrs.Provider that is specifically configured in a required_providers block.
func (pq *ProvidersQualification) AddExplicitProvider(provider addrs.Provider) {
	if pq.Explicit == nil {
		pq.Explicit = map[addrs.Provider]struct{}{}
	}
	pq.Explicit[provider] = struct{}{}
}

// Merge takes the requirements in the receiver and the requirements in the
// other given value and produces a new set of requirements that combines
// all of the requirements of both.
//

Review comment (suggestion):
I would only care about this if there isn't already a dependency from this package on HCL, but if this package is _only_ depending on HCL to get `hcl.Range` then I might suggest using [`tfdiags.SourceRange`](https://pkg.go.dev/github.com/opentofu/opentofu/internal/tfdiags#SourceRange) instead, which is what we tend to use to represent source ranges in situations where we're not otherwise working _directly_ with HCL's types.

There are conversion functions in both directions between `hcl.Range` and `tfdiags.SourceRange`, so using it tends to just cause a few extra conversion calls. A common place we've drawn this line in the past is that `package configs` primarily uses HCL's types (since its overall job is to map generic HCL stuff to OpenTofu-domain-specific-concepts) while most other packages prefer to use `tfdiags` types. That's more a guideline than a rule, though.


Code after:
// VersionConstraints represents a set of version constraints, which can
// define the membership of a VersionSet by exclusion.
type VersionConstraints = constraints.IntersectionSpec

// Warnings represents a list of warnings returned by a Registry source.
type Warnings = []string

// Requirements gathers together requirements for many different providers
// into a single data structure, as a convenient way to represent the full
// set of requirements for a particular configuration or state or both.
//
// If an entry in a Requirements has a zero-length VersionConstraints then
// that indicates that the provider is required but that any version is
// acceptable. That's different than a provider being absent from the map
// altogether, which means that it is not required at all.
type Requirements map[addrs.Provider]VersionConstraints

// ProvidersQualification is storing the implicit/explicit reference qualification of the providers.
// This is necessary to be able to warn the user when the resources are referencing a provider that
// is not specifically defined in a required_providers block. When the implicitly referenced
// provider is tried to be downloaded without a specific provider requirement, it will be tried
// from the default namespace (hashicorp), failing to download it when it does not exist in the default namespace.
// Therefore, we want to let the user know what resources are generating this situation.
type ProvidersQualification struct {
	Implicit map[addrs.Provider][]ResourceRef
	Explicit map[addrs.Provider]struct{}
}

type ResourceRef struct {
	CfgRes            addrs.ConfigResource
	Ref               tfdiags.SourceRange
	ProviderAttribute bool
}

// AddImplicitProvider saves an addrs.Provider with the place in the configuration where this is generated from.
func (pq *ProvidersQualification) AddImplicitProvider(provider addrs.Provider, ref ResourceRef) {
	if pq.Implicit == nil {
		pq.Implicit = map[addrs.Provider][]ResourceRef{}
	}
	// This is avoiding adding the implicit reference of the provider if this is already explicitly configured.
	// Done this way, because when collecting these qualifications, if there are at least 2 resources (A from root module and B from an imported module),
	// root module could have no explicit definition but the module of B could have an explicit one. But in case none of the modules is having
	// an explicit definition, we want to gather all the resources that are implicitly referencing a provider.
	if _, ok := pq.Explicit[provider]; ok {
		return
	}
	refs := pq.Implicit[provider]
	refs = append(refs, ref)
	pq.Implicit[provider] = refs
}