// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: BUSL-1.1 package stackaddrs import ( "fmt" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/terraform/internal/tfdiags" ) // Reference describes a reference expression found in the configuration, // capturing what it referred to and where it was found in source code. type Reference struct { Target Referenceable SourceRange tfdiags.SourceRange } // ParseReference raises a raw absolute traversal into a higher-level reference, // or returns error diagnostics explaining why it cannot. // // The returned traversal is a relative traversal covering the remainder of // the given traversal after the part captured into the returned reference, // in case the caller wants to do further validation or analysis of the // subsequent steps. func ParseReference(traversal hcl.Traversal) (Reference, hcl.Traversal, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics var ret Reference switch rootName := traversal.RootName(); rootName { case "var": name, rng, remain, diags := parseSingleAttrRef(traversal) ret.Target = InputVariable{Name: name} ret.SourceRange = tfdiags.SourceRangeFromHCL(rng) return ret, remain, diags case "local": name, rng, remain, diags := parseSingleAttrRef(traversal) ret.Target = LocalValue{Name: name} ret.SourceRange = tfdiags.SourceRangeFromHCL(rng) return ret, remain, diags case "component": name, rng, remain, diags := parseSingleAttrRef(traversal) ret.Target = Component{Name: name} ret.SourceRange = tfdiags.SourceRangeFromHCL(rng) return ret, remain, diags case "stack": name, rng, remain, diags := parseSingleAttrRef(traversal) ret.Target = StackCall{Name: name} ret.SourceRange = tfdiags.SourceRangeFromHCL(rng) return ret, remain, diags case "provider": target, rng, remain, diags := parseProviderRef(traversal) ret.Target = target ret.SourceRange = tfdiags.SourceRangeFromHCL(rng) return ret, remain, diags case "each", "count": attrName, rng, remain, diags := parseSingleAttrRef(traversal) if diags.HasErrors() { return ret, nil, diags } ret.SourceRange = tfdiags.SourceRangeFromHCL(rng) switch rootName { case "each": switch attrName { case "key": ret.Target = EachKey return ret, remain, diags case "value": ret.Target = EachValue return ret, remain, diags } case "count": switch attrName { case "index": ret.Target = CountIndex return ret, remain, diags } } // If we get here then rootName and attrName are not a valid combination. diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Reference to unknown symbol", Detail: fmt.Sprintf("The object %q has no attribute named %q.", rootName, attrName), Subject: traversal[1].SourceRange().Ptr(), }) return ret, nil, diags case "self": ret.Target = Self ret.SourceRange = tfdiags.SourceRangeFromHCL(traversal[0].SourceRange()) return ret, traversal[1:], diags case "terraform": attrName, rng, remain, diags := parseSingleAttrRef(traversal) if diags.HasErrors() { return ret, nil, diags } ret.SourceRange = tfdiags.SourceRangeFromHCL(rng) switch attrName { case "applying": ret.Target = TerraformApplying return ret, remain, diags default: diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Reference to unknown symbol", Detail: fmt.Sprintf("The object %q has no attribute named %q.", rootName, attrName), Subject: traversal[1].SourceRange().Ptr(), }) return ret, remain, diags } case "_test_only_global": name, rng, remain, diags := parseSingleAttrRef(traversal) ret.Target = TestOnlyGlobal{Name: name} ret.SourceRange = tfdiags.SourceRangeFromHCL(rng) return ret, remain, diags default: diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Reference to unknown symbol", Detail: fmt.Sprintf("There is no symbol %q defined in the current scope.", rootName), Subject: traversal[0].SourceRange().Ptr(), }) return ret, nil, diags } } func parseSingleAttrRef(traversal hcl.Traversal) (string, hcl.Range, hcl.Traversal, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics root := traversal.RootName() rootRange := traversal[0].SourceRange() if len(traversal) < 2 { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid reference", Detail: fmt.Sprintf("The %q object cannot be accessed directly. Instead, access one of its attributes.", root), Subject: &rootRange, }) return "", hcl.Range{}, nil, diags } if attrTrav, ok := traversal[1].(hcl.TraverseAttr); ok { return attrTrav.Name, hcl.RangeBetween(rootRange, attrTrav.SrcRange), traversal[2:], diags } diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid reference", Detail: fmt.Sprintf("The %q object does not support this operation.", root), Subject: traversal[1].SourceRange().Ptr(), }) return "", hcl.Range{}, nil, diags } func parseProviderRef(traversal hcl.Traversal) (ProviderConfigRef, hcl.Range, hcl.Traversal, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics if len(traversal) < 3 { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid reference", Detail: "The \"provider\" symbol must be followed by two attribute access operations, selecting a provider type and a provider configuration name.", Subject: traversal.SourceRange().Ptr(), }) return ProviderConfigRef{}, hcl.Range{}, nil, diags } if typeTrav, ok := traversal[1].(hcl.TraverseAttr); ok { if nameTrav, ok := traversal[2].(hcl.TraverseAttr); ok { ret := ProviderConfigRef{ ProviderLocalName: typeTrav.Name, Name: nameTrav.Name, } return ret, traversal.SourceRange(), traversal[3:], diags } else { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid reference", Detail: "The \"provider\" object's attributes do not support this operation.", Subject: traversal[1].SourceRange().Ptr(), }) } } else { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid reference", Detail: "The \"provider\" object does not support this operation.", Subject: traversal[1].SourceRange().Ptr(), }) } return ProviderConfigRef{}, hcl.Range{}, nil, diags } func (r Reference) Absolute(stack StackInstance) AbsReference { return AbsReference{ Stack: stack, Ref: r, } } // AbsReference is an absolute form of [Reference] that is to be resolved // in the global scope of a particular stack. // // It's not meaningful to use this type for references to objects that exist // only in a more specific scope, such as each.key, each.value, etc, because // those would require additional information about exactly which object // they are being resolved in terms of. type AbsReference struct { Stack StackInstance Ref Reference } func (r AbsReference) Target() AbsReferenceable { return AbsReferenceable{ Stack: r.Stack, Item: r.Ref.Target, } } func (r AbsReference) SourceRange() tfdiags.SourceRange { return r.Ref.SourceRange }