vendor: update github.com/hashicorp/hcl/v2 to v2.19.1

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2023-10-19 14:49:10 +02:00
parent ad674e2666
commit 34b9a629a0
157 changed files with 20123 additions and 5438 deletions

16
vendor/github.com/hashicorp/hcl/v2/.copywrite.hcl generated vendored Normal file
View File

@ -0,0 +1,16 @@
schema_version = 1
project {
license = "MPL-2.0"
copyright_year = 2014
# (OPTIONAL) A list of globs that should not have copyright/license headers.
# Supports doublestar glob patterns for more flexibility in defining which
# files or folders should be ignored
header_ignore = [
"hclsyntax/fuzz/testdata/**",
"hclwrite/fuzz/testdata/**",
"json/fuzz/testdata/**",
"specsuite/tests/**",
]
}

View File

@ -1,13 +1,176 @@
# HCL Changelog
## v2.8.2 (Unreleased)
## v2.19.0 (October 16, 2023)
### Enhancements
* ext/dynblock: `dynblock.Expand` now supports an optional hook for calling applications to check and potentially veto (by returning error diagnostics) particular `for_each` values. The behavior is unchanged for callers that don't set the new option. ([#634](https://github.com/hashicorp/hcl/pull/634))
### Bugs Fixed
* hclsyntax: Further fixes for treatment of "marked" values in the conditional expression, and better tracking of refined values into the conditional expression results, building on the fixes from v2.18.1. ([#633](https://github.com/hashicorp/hcl/pull/633))
## v2.18.1 (October 5, 2023)
### Bugs Fixed
* hclsyntax: Conditional expressions will no longer panic when one or both of their results are "marked", as is the case for situations like how HashiCorp Terraform tracks its concept of "sensitive values". ([#630](https://github.com/hashicorp/hcl/pull/630))
## v2.18.0 (August 30, 2023)
### Enhancements
* HCL now uses the tables from Unicode 15 when performing string normalization and character segmentation. HCL was previously using the Unicode 13 tables.
For calling applications where consistent Unicode support is important, consider also upgrading to Go 1.21 at the same time as adopting HCL v2.18.0 so that the standard library unicode tables (used for case folding, etc) will also be from Unicode 15.
## v2.17.1 (August 30, 2023)
### Enhancements
* hclsyntax: When evaluating string templates that have a long known constant prefix, HCL will truncate the known prefix to avoid creating excessively-large refinements. String prefix refinements are intended primarily for relatively-short fixed prefixes, such as `https://` at the start of a URL known to use that scheme. ([#617](https://github.com/hashicorp/hcl/pull/617))
* ext/tryfunc: The "try" and "can" functions now handle unknown values slightly more precisely, and so can return known values in more situations when given expressions referring to unknown symbols. ([#622](https://github.com/hashicorp/hcl/pull/622))
### Bugs Fixed
* ext/typeexpr: Will no longer try to refine unknown values of unknown type when dealing with a user-specified type constraint containing the `any` keyword, avoiding an incorrect panic at runtime. ([#625](https://github.com/hashicorp/hcl/pull/625))
* ext/typeexpr: Now correctly handles attempts to declare the same object type attribute multiple times by returning an error. Previously this could potentially panic by creating an incoherent internal state. ([#624](https://github.com/hashicorp/hcl/pull/624))
## v2.17.0 (May 31, 2023)
### Enhancements
* HCL now uses a newer version of the upstream `cty` library which has improved treatment of unknown values: it can now track additional optional information that reduces the range of an unknown value, which allows some operations against unknown values to return known or partially-known results. ([#590](https://github.com/hashicorp/hcl/pull/590))
**Note:** This change effectively passes on [`cty`'s notion of backward compatibility](https://github.com/zclconf/go-cty/blob/main/COMPATIBILITY.md) whereby unknown values can become "more known" in later releases. In particular, if your caller is using `cty.Value.RawEquals` in its tests against the results of operations with unknown values then you may see those tests begin failing after upgrading, due to the values now being more "refined".
If so, you should review the refinements with consideration to [the `cty` refinements docs](https://github.com/zclconf/go-cty/blob/7dcbae46a6f247e983efb1fa774d2bb68781a333/docs/refinements.md) and update your expected results to match only if the reported refinements seem correct for the given situation. The `RawEquals` method is intended only for making exact value comparisons in test cases, so main application code should not use it; use `Equals` instead for real logic, which will take refinements into account automatically.
## v2.16.2 (March 9, 2023)
### Bugs Fixed
* ext/typeexpr: Verify type assumptions when applying default values, and ignore input values that do not match type assumptions. ([#594](https://github.com/hashicorp/hcl/pull/594))
## v2.16.1 (February 13, 2023)
### Bugs Fixed
* hclsyntax: Report correct `Range.End` for `FunctionCall` with incomplete argument ([#588](https://github.com/hashicorp/hcl/pull/588))
## v2.16.0 (January 30, 2023)
### Enhancements
* ext/typeexpr: Modify the `Defaults` functionality to implement additional flexibility. HCL will now upcast lists and sets into tuples, and maps into objects, when applying default values if the applied defaults cause the elements within a target collection to have differing types. Previously, this would have resulted in a panic, now HCL will return a modified overall type. ([#574](https://github.com/hashicorp/hcl/pull/574))
Users should return to the advice provided by v2.14.0, and apply the go-cty convert functionality *after* setting defaults on a given `cty.Value`, rather than before.
* hclfmt: Avoid rewriting unchanged files. ([#576](https://github.com/hashicorp/hcl/pull/576))
* hclsyntax: Simplify the AST for certain string expressions. ([#584](https://github.com/hashicorp/hcl/pull/584))
### Bugs Fixed
* hclwrite: Fix data race in `formatSpaces`. ([#511](https://github.com/hashicorp/hcl/pull/511))
## v2.15.0 (November 10, 2022)
### Bugs Fixed
* ext/typeexpr: Skip null objects when applying defaults. This prevents crashes when null objects are creating inside collections, and stops incomplete objects being created with only optional attributes set. ([#567](https://github.com/hashicorp/hcl/pull/567))
* ext/typeexpr: Ensure default values do not have optional metadata attached. This prevents crashes when default values are inserted into concrete go-cty values that have also been stripped of their optional metadata. ([#568](https://github.com/hashicorp/hcl/pull/568))
### Enhancements
* ext/typeexpr: With the [go-cty](https://github.com/zclconf/go-cty) upstream depenendency updated to v1.12.0, the `Defaults` struct and associated functions can apply additional and more flexible 'unsafe' conversions (examples include tuples into collections such as lists and sets, and additional safety around null and dynamic values). ([#564](https://github.com/hashicorp/hcl/pull/564))
* ext/typeexpr: With the [go-cty](https://github.com/zclconf/go-cty) upstream depenendency updated to v1.12.0, users should now apply the go-cty convert functionality *before* setting defaults on a given `cty.Value`, rather than after, if they require a specific `cty.Type`. ([#564](https://github.com/hashicorp/hcl/pull/564))
## v2.14.1 (September 23, 2022)
### Bugs Fixed
* ext/typeexpr: Type convert defaults for optional object attributes when applying them. This prevents crashes in certain cases when the objects in question are part of a collection. ([#555](https://github.com/hashicorp/hcl/pull/555))
## v2.14.0 (September 1, 2022)
### Enhancements
* ext/typeexpr: Added support for optional object attributes to `TypeConstraint`. Attributes can be wrapped in the special `optional(…)` modifier, allowing the attribute to be omitted while still meeting the type constraint. For more information, [cty's documentation on conversion between object types](https://github.com/zclconf/go-cty/blob/main/docs/convert.md#conversion-between-object-types). ([#549](https://github.com/hashicorp/hcl/pull/549))
* ext/typeexpr: New function: `TypeConstraintWithDefaults`. In this mode, the `optional(…)` modifier accepts a second argument which can be used as the default value for omitted object attributes. The function returns both a `cty.Type` and associated `Defaults`, the latter of which has an `Apply` method to apply defaults to a given value. ([#549](https://github.com/hashicorp/hcl/pull/549))
## v2.13.0 (June 22, 2022)
### Enhancements
* hcl: `hcl.Diagnostic` now has an additional field `Extra` which is intended for carrying arbitrary supporting data ("extra information") related to the diagnostic message, intended to allow diagnostic renderers to optionally tailor the presentation of messages for particular situations. ([#539](https://github.com/hashicorp/hcl/pull/539))
* hclsyntax: When an error occurs during a function call, the returned diagnostics will include _extra information_ (as described in the previous point) about which function was being called and, if the message is about an error returned by the function itself, that raw `error` value without any post-processing. ([#539](https://github.com/hashicorp/hcl/pull/539))
### Bugs Fixed
* hclwrite: Fixed a potential data race for any situation where `hclwrite.Format` runs concurrently with itself. ([#534](https://github.com/hashicorp/hcl/pull/534))
## v2.12.0 (April 22, 2022)
### Enhancements
* hclsyntax: Evaluation of conditional expressions will now produce more precise error messages about inconsistencies between the types of the true and false result expressions, particularly in cases where both are of the same structural type kind but differ in their nested elements. ([#530](https://github.com/hashicorp/hcl/pull/530))
* hclsyntax: The lexer will no longer allocate a small object on the heap for each token. Instead, in that situation it will allocate only when needed to return a diagnostic message with source location information. ([#490](https://github.com/hashicorp/hcl/pull/490))
* hclwrite: New functions `TokensForTuple`, `TokensForObject`, and `TokensForFunctionCall` allow for more easily constructing the three constructs which are supported for static analysis and which HCL-based languages typically use in contexts where an expression is used only for its syntax, and not evaluated to produce a real value. For example, these new functions together are sufficient to construct all valid type constraint expressions from [the Type Expressions Extension](./ext/typeexpr/), which is the basis of variable type constraints in the Terraform language at the time of writing. ([#502](https://github.com/hashicorp/hcl/pull/502))
* json: New functions `IsJSONExpression` and `IsJSONBody` to determine if a given expression or body was created by the JSON syntax parser. In normal situations it's better not to worry about what syntax a particular expression/body originated in, but this can be useful in some trickier cases where an application needs to shim for backwards-compatibility or for static analysis that needs to have special handling of the JSON syntax's embedded expression/template conventions. ([#524](https://github.com/hashicorp/hcl/pull/524))
### Bugs Fixed
* gohcl: Fix docs about supported types for blocks. ([#507](https://github.com/hashicorp/hcl/pull/507))
## v2.11.1 (December 1, 2021)
### Bugs Fixed
* hclsyntax: The type for an upgraded unknown value with a splat expression cannot be known ([#495](https://github.com/hashicorp/hcl/pull/495))
## v2.11.0 (December 1, 2021)
### Enhancements
* hclsyntax: Various error messages related to unexpectedly reaching end of file while parsing a delimited subtree will now return specialized messages describing the opening tokens as "unclosed", instead of returning a generic diagnostic that just happens to refer to the empty source range at the end of the file. This gives better feedback when error messages are being presented alongside a source code snippet, as is common in HCL-based applications, because it shows which innermost container the parser was working on when it encountered the error. ([#492](https://github.com/hashicorp/hcl/pull/492))
### Bugs Fixed
* hclsyntax: Upgrading an unknown single value to a list using a splat expression must return unknown ([#493](https://github.com/hashicorp/hcl/pull/493))
## v2.10.1 (July 21, 2021)
* dynblock: Decode unknown dynamic blocks in order to obtain any diagnostics even though the decoded value is not used ([#476](https://github.com/hashicorp/hcl/pull/476))
* hclsyntax: Calling functions is now more robust in the face of an incorrectly-implemented function which returns a `function.ArgError` whose argument index is out of range for the length of the arguments. Previously this would often lead to a panic, but now it'll return a less-precice error message instead. Functions that return out-of-bounds argument indices still ought to be fixed so that the resulting error diagnostics can be as precise as possible. ([#472](https://github.com/hashicorp/hcl/pull/472))
* hclsyntax: Ensure marks on unknown values are maintained when processing string templates. ([#478](https://github.com/hashicorp/hcl/pull/478))
* hcl: Improved error messages for various common error situtions in `hcl.Index` and `hcl.GetAttr`. These are part of the implementation of indexing and attribute lookup in the native syntax expression language too, so the new error messages will apply to problems using those operators. ([#474](https://github.com/hashicorp/hcl/pull/474))
## v2.10.0 (April 20, 2021)
### Enhancements
* dynblock,hcldec: Using dynblock in conjunction with hcldec can now decode blocks with unknown dynamic for_each arguments as entirely unknown values ([#461](https://github.com/hashicorp/hcl/pull/461))
* hclsyntax: Some syntax errors during parsing of the inside of `${` ... `}` template interpolation sequences will now produce an extra hint message about the need to escape as `$${` when trying to include interpolation syntax for other languages like shell scripting, AWS IAM policies, etc. ([#462](https://github.com/hashicorp/hcl/pull/462))
## v2.9.1 (March 10, 2021)
### Bugs Fixed
* hclsyntax: Fix panic for marked index value. ([#451](https://github.com/hashicorp/hcl/pull/451))
## v2.9.0 (February 23, 2021)
### Enhancements
* HCL's native syntax and JSON scanners -- and thus all of the other parsing components that build on top of them -- are now using Unicode 13 rules for text segmentation when counting text characters for the purpose of reporting source location columns. Previously HCL was using Unicode 12. Unicode 13 still uses the same algorithm but includes some additions to the character tables the algorithm is defined in terms of, to properly categorize new characters defined in Unicode 13.
## v2.8.2 (January 6, 2021)
### Bugs Fixed
* hclsyntax: Fix panic for marked collection splat. ([#436](https://github.com/hashicorp/hcl/pull/436))
* hclsyntax: Fix panic for marked template loops. ([#437](https://github.com/hashicorp/hcl/pull/437))
* hclsyntax: Fix `for` expression marked conditional.([#438](https://github.com/hashicorp/hcl/pull/438))
* hclsyntax: Mark objects with keys that are sensitive ([#440](https://github.com/hashicorp/hcl/pull/440))
* hclsyntax: Fix `for` expression marked conditional. ([#438](https://github.com/hashicorp/hcl/pull/438))
* hclsyntax: Mark objects with keys that are sensitive. ([#440](https://github.com/hashicorp/hcl/pull/440))
## v2.8.1 (December 17, 2020)

View File

@ -1,3 +1,5 @@
Copyright (c) 2014 HashiCorp, Inc.
Mozilla Public License, version 2.0
1. Definitions

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (
@ -63,6 +66,28 @@ type Diagnostic struct {
// case of colliding names.
Expression Expression
EvalContext *EvalContext
// Extra is an extension point for additional machine-readable information
// about this problem.
//
// Recipients of diagnostic objects may type-assert this value with
// specific interface types they know about to discover if any additional
// information is available that is interesting for their use-case.
//
// Extra is always considered to be optional extra information and so a
// diagnostic message should still always be fully described (from the
// perspective of a human who understands the language the messages are
// written in) by the other fields in case a particular recipient.
//
// Functions that return diagnostics with Extra populated should typically
// document that they place values implementing a particular interface,
// rather than a concrete type, and define that interface such that its
// methods can dynamically indicate a lack of support at runtime even
// if the interface happens to be statically available. An Extra
// type that wraps other Extra values should additionally implement
// interface DiagnosticExtraUnwrapper to return the value they are wrapping
// so that callers can access inner values to type-assert against.
Extra interface{}
}
// Diagnostics is a list of Diagnostic instances.
@ -141,3 +166,24 @@ type DiagnosticWriter interface {
WriteDiagnostic(*Diagnostic) error
WriteDiagnostics(Diagnostics) error
}
// DiagnosticExtraUnwrapper is an interface implemented by values in the
// Extra field of Diagnostic when they are wrapping another "Extra" value that
// was generated downstream.
//
// Diagnostic recipients which want to examine "Extra" values to sniff for
// particular types of extra data can either type-assert this interface
// directly and repeatedly unwrap until they recieve nil, or can use the
// helper function DiagnosticExtra.
type DiagnosticExtraUnwrapper interface {
// If the reciever is wrapping another "diagnostic extra" value, returns
// that value. Otherwise returns nil to indicate dynamically that nothing
// is wrapped.
//
// The "nothing is wrapped" condition can be signalled either by this
// method returning nil or by a type not implementing this interface at all.
//
// Implementers should never create unwrap "cycles" where a nested extra
// value returns a value that was also wrapping it.
UnwrapDiagnosticExtra() interface{}
}

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (

View File

@ -0,0 +1,42 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//go:build go1.18
// +build go1.18
package hcl
// This file contains additional diagnostics-related symbols that use the
// Go 1.18 type parameters syntax and would therefore be incompatible with
// Go 1.17 and earlier.
// DiagnosticExtra attempts to retrieve an "extra value" of type T from the
// given diagnostic, if either the diag.Extra field directly contains a value
// of that type or the value implements DiagnosticExtraUnwrapper and directly
// or indirectly returns a value of that type.
//
// Type T should typically be an interface type, so that code which generates
// diagnostics can potentially return different implementations of the same
// interface dynamically as needed.
//
// If a value of type T is found, returns that value and true to indicate
// success. Otherwise, returns the zero value of T and false to indicate
// failure.
func DiagnosticExtra[T any](diag *Diagnostic) (T, bool) {
extra := diag.Extra
var zero T
for {
if ret, ok := extra.(T); ok {
return ret, true
}
if unwrap, ok := extra.(DiagnosticExtraUnwrapper); ok {
// If our "extra" implements DiagnosticExtraUnwrapper then we'll
// unwrap one level and try this again.
extra = unwrap.UnwrapDiagnosticExtra()
} else {
return zero, false
}
}
}

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Package hcl contains the main modelling types and general utility functions
// for HCL.
//

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
// ExprCall tests if the given expression is a function call and,

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
// ExprList tests if the given expression is a static list construct and,

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
// ExprMap tests if the given expression is a static map construct and,

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
type unwrapExpression interface {

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Package customdecode contains a HCL extension that allows, in certain
// contexts, expression evaluation to be overridden by custom static analysis.
//

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package customdecode
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Package tryfunc contains some optional functions that can be exposed in
// HCL-based languages to allow authors to test whether a particular expression
// can succeed and take dynamic action based on that result.
@ -69,18 +72,35 @@ func try(args []cty.Value) (cty.Value, error) {
var diags hcl.Diagnostics
for _, arg := range args {
closure := customdecode.ExpressionClosureFromVal(arg)
if dependsOnUnknowns(closure.Expression, closure.EvalContext) {
// We can't safely decide if this expression will succeed yet,
// and so our entire result must be unknown until we have
// more information.
return cty.DynamicVal, nil
}
v, moreDiags := closure.Value()
diags = append(diags, moreDiags...)
if moreDiags.HasErrors() {
continue // try the next one, if there is one to try
// If there's an error we know it will always fail and can
// continue. A more refined value will not remove an error from
// the expression.
continue
}
if !v.IsWhollyKnown() {
// If there are any unknowns in the value at all, we cannot be
// certain that the final value will be consistent or have the same
// type, so wee need to be conservative and return a dynamic value.
// There are two different classes of failure that can happen when
// an expression transitions from unknown to known; an operation on
// a dynamic value becomes invalid for the type once the type is
// known, or an index expression on a collection fails once the
// collection value is known. These changes from a
// valid-partially-unknown expression to an invalid-known
// expression can produce inconsistent results by changing which
// "try" argument is returned, which may be a collection with
// different previously known values, or a different type entirely
// ("try" does not require consistent argument types)
return cty.DynamicVal, nil
}
return v, nil // ignore any accumulated diagnostics if one succeeds
}
@ -108,43 +128,17 @@ func try(args []cty.Value) (cty.Value, error) {
func can(arg cty.Value) (cty.Value, error) {
closure := customdecode.ExpressionClosureFromVal(arg)
if dependsOnUnknowns(closure.Expression, closure.EvalContext) {
// Can't decide yet, then.
return cty.UnknownVal(cty.Bool), nil
}
_, diags := closure.Value()
v, diags := closure.Value()
if diags.HasErrors() {
return cty.False, nil
}
if !v.IsWhollyKnown() {
// If the value is not wholly known, we still cannot be certain that
// the expression was valid. There may be yet index expressions which
// will fail once values are completely known.
return cty.UnknownVal(cty.Bool), nil
}
return cty.True, nil
}
// dependsOnUnknowns returns true if any of the variables that the given
// expression might access are unknown values or contain unknown values.
//
// This is a conservative result that prefers to return true if there's any
// chance that the expression might derive from an unknown value during its
// evaluation; it is likely to produce false-positives for more complex
// expressions involving deep data structures.
func dependsOnUnknowns(expr hcl.Expression, ctx *hcl.EvalContext) bool {
for _, traversal := range expr.Variables() {
val, diags := traversal.TraverseAbs(ctx)
if diags.HasErrors() {
// If the traversal returned a definitive error then it must
// not traverse through any unknowns.
continue
}
if !val.IsWhollyKnown() {
// The value will be unknown if either it refers directly to
// an unknown value or if the traversal moves through an unknown
// collection. We're using IsWhollyKnown, so this also catches
// situations where the traversal refers to a compound data
// structure that contains any unknown values. That's important,
// because during evaluation the expression might evaluate more
// deeply into this structure and encounter the unknowns.
return true
}
}
return false
}

View File

@ -66,6 +66,27 @@ types with weird attributes generally show up only from arbitrary object
constructors in configuration files, which are usually treated either as maps
or as the dynamic pseudo-type.
### Optional Object Attributes
As part of object expressions attributes can be marked as optional. Missing
object attributes would typically result in an error when type constraints are
validated or used. Optional missing attributes, however, would not result in an
error. The `cty` ["convert" function](#the-convert-cty-function) will populate
missing optional attributes with null values.
For example:
* `object({name=string,age=optional(number)})`
Optional attributes can also be specified with default values. The
`TypeConstraintWithDefaults` function will return a `Defaults` object that can
be used to populate missing optional attributes with defaults in a given
`cty.Value`.
For example:
* `object({name=string,age=optional(number, 0)})`
## Type Constraints as Values
Along with defining a convention for writing down types using HCL expression

View File

@ -0,0 +1,254 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package typeexpr
import (
"sort"
"strconv"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
)
// Defaults represents a type tree which may contain default values for
// optional object attributes at any level. This is used to apply nested
// defaults to a given cty.Value before converting it to a concrete type.
type Defaults struct {
// Type of the node for which these defaults apply. This is necessary in
// order to determine how to inspect the Defaults and Children collections.
Type cty.Type
// DefaultValues contains the default values for each object attribute,
// indexed by attribute name.
DefaultValues map[string]cty.Value
// Children is a map of Defaults for elements contained in this type. This
// only applies to structural and collection types.
//
// The map is indexed by string instead of cty.Value because cty.Number
// instances are non-comparable, due to embedding a *big.Float.
//
// Collections have a single element type, which is stored at key "".
Children map[string]*Defaults
}
// Apply walks the given value, applying specified defaults wherever optional
// attributes are missing. The input and output values may have different
// types, and the result may still require type conversion to the final desired
// type.
//
// This function is permissive and does not report errors, assuming that the
// caller will have better context to report useful type conversion failure
// diagnostics.
func (d *Defaults) Apply(val cty.Value) cty.Value {
return d.apply(val)
}
func (d *Defaults) apply(v cty.Value) cty.Value {
// We don't apply defaults to null values or unknown values. To be clear,
// we will overwrite children values with defaults if they are null but not
// if the actual value is null.
if !v.IsKnown() || v.IsNull() {
return v
}
// Also, do nothing if we have no defaults to apply.
if len(d.DefaultValues) == 0 && len(d.Children) == 0 {
return v
}
v, marks := v.Unmark()
switch {
case v.Type().IsSetType(), v.Type().IsListType(), v.Type().IsTupleType():
values := d.applyAsSlice(v)
if v.Type().IsSetType() {
if len(values) == 0 {
v = cty.SetValEmpty(v.Type().ElementType())
break
}
if converts := d.unifyAsSlice(values); len(converts) > 0 {
v = cty.SetVal(converts).WithMarks(marks)
break
}
} else if v.Type().IsListType() {
if len(values) == 0 {
v = cty.ListValEmpty(v.Type().ElementType())
break
}
if converts := d.unifyAsSlice(values); len(converts) > 0 {
v = cty.ListVal(converts)
break
}
}
v = cty.TupleVal(values)
case v.Type().IsObjectType(), v.Type().IsMapType():
values := d.applyAsMap(v)
for key, defaultValue := range d.DefaultValues {
if value, ok := values[key]; !ok || value.IsNull() {
if defaults, ok := d.Children[key]; ok {
values[key] = defaults.apply(defaultValue)
continue
}
values[key] = defaultValue
}
if defaultRng := defaultValue.Range(); defaultRng.DefinitelyNotNull() && values[key].Type() != cty.DynamicPseudoType {
values[key] = values[key].RefineNotNull()
}
}
if v.Type().IsMapType() {
if len(values) == 0 {
v = cty.MapValEmpty(v.Type().ElementType())
break
}
if converts := d.unifyAsMap(values); len(converts) > 0 {
v = cty.MapVal(converts)
break
}
}
v = cty.ObjectVal(values)
}
return v.WithMarks(marks)
}
func (d *Defaults) applyAsSlice(value cty.Value) []cty.Value {
var elements []cty.Value
for ix, element := range value.AsValueSlice() {
if childDefaults := d.getChild(ix); childDefaults != nil {
element = childDefaults.apply(element)
elements = append(elements, element)
continue
}
elements = append(elements, element)
}
return elements
}
func (d *Defaults) applyAsMap(value cty.Value) map[string]cty.Value {
elements := make(map[string]cty.Value)
for key, element := range value.AsValueMap() {
if childDefaults := d.getChild(key); childDefaults != nil {
elements[key] = childDefaults.apply(element)
continue
}
elements[key] = element
}
return elements
}
func (d *Defaults) getChild(key interface{}) *Defaults {
// Children for tuples are keyed by an int.
// Children for objects are keyed by a string.
// Children for maps, lists, and sets are always keyed by the empty string.
//
// For maps and objects the supplied key type is a string type.
// For lists, sets, and tuples the supplied key type is an int type.
//
// The callers of the defaults package could, in theory, pass a value in
// where the types expected based on the defaults do not match the actual
// type in the value. In this case, we get a mismatch between what the
// defaults package expects the key to be, and which type it actually is.
//
// In the event of such a mismatch, we just won't apply defaults. Instead,
// relying on the user later calling go-cty.Convert to detect this same
// error (as documented). In this case we'd just want to return nil to
// indicate either there are no defaults or we can't work out how to apply
// them. Both of these outcomes are treated the same by the rest of the
// package.
//
// For the above reasons it isn't necessarily safe to just rely on a single
// metric for working out how we should retrieve the key. If the defaults
// type is a tuple we can't just assume the supplied key will be an int (as
// the concrete value actually supplied by the user could be an object or a
// map). Similarly, if the supplied key is an int we can't just assume we
// should treat the type as a tuple (as a list would also specify an int,
// but we should return the children keyed by the empty string rather than
// the index).
switch concrete := key.(type) {
case int:
if d.Type.IsTupleType() {
// If the type is an int, and our defaults are expecting a tuple
// then we return the children for the tuple at the index.
return d.Children[strconv.Itoa(concrete)]
}
case string:
if d.Type.IsObjectType() {
// If the type is a string, and our defaults are expecting an object
// then we return the children for the object at the key.
return d.Children[concrete]
}
}
// Otherwise, either our defaults are expecting this to be a map, list, or
// set or the type our defaults expecting didn't line up with something we
// can convert between. So, either we want to return the child keyed by
// the empty string (in the first case) or nil (in the second case).
// Luckily, Golang maps return nil when referencing something that doesn't
// exist. So, we can just try and retrieve the child at the empty key and
// if it doesn't exist then that's fine since we'd just return nil anyway.
return d.Children[""]
}
func (d *Defaults) unifyAsSlice(values []cty.Value) []cty.Value {
var types []cty.Type
for _, value := range values {
types = append(types, value.Type())
}
unify, conversions := convert.UnifyUnsafe(types)
if unify == cty.NilType {
return nil
}
var converts []cty.Value
for ix := 0; ix < len(conversions); ix++ {
if conversions[ix] == nil {
converts = append(converts, values[ix])
continue
}
converted, err := conversions[ix](values[ix])
if err != nil {
return nil
}
converts = append(converts, converted)
}
return converts
}
func (d *Defaults) unifyAsMap(values map[string]cty.Value) map[string]cty.Value {
var keys []string
for key := range values {
keys = append(keys, key)
}
sort.Strings(keys)
var types []cty.Type
for _, key := range keys {
types = append(types, values[key].Type())
}
unify, conversions := convert.UnifyUnsafe(types)
if unify == cty.NilType {
return nil
}
converts := make(map[string]cty.Value)
for ix, key := range keys {
if conversions[ix] == nil {
converts[key] = values[key]
continue
}
var err error
if converts[key], err = conversions[ix](values[key]); err != nil {
return nil
}
}
return converts
}

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Package typeexpr extends HCL with a convention for describing HCL types
// within configuration files.
//

View File

@ -1,53 +1,60 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package typeexpr
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
"github.com/hashicorp/hcl/v2"
)
const invalidTypeSummary = "Invalid type specification"
// getType is the internal implementation of both Type and TypeConstraint,
// using the passed flag to distinguish. When constraint is false, the "any"
// keyword will produce an error.
func getType(expr hcl.Expression, constraint bool) (cty.Type, hcl.Diagnostics) {
// getType is the internal implementation of Type, TypeConstraint, and
// TypeConstraintWithDefaults, using the passed flags to distinguish. When
// `constraint` is true, the "any" keyword can be used in place of a concrete
// type. When `withDefaults` is true, the "optional" call expression supports
// an additional argument describing a default value.
func getType(expr hcl.Expression, constraint, withDefaults bool) (cty.Type, *Defaults, hcl.Diagnostics) {
// First we'll try for one of our keywords
kw := hcl.ExprAsKeyword(expr)
switch kw {
case "bool":
return cty.Bool, nil
return cty.Bool, nil, nil
case "string":
return cty.String, nil
return cty.String, nil, nil
case "number":
return cty.Number, nil
return cty.Number, nil, nil
case "any":
if constraint {
return cty.DynamicPseudoType, nil
return cty.DynamicPseudoType, nil, nil
}
return cty.DynamicPseudoType, hcl.Diagnostics{{
return cty.DynamicPseudoType, nil, hcl.Diagnostics{{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: fmt.Sprintf("The keyword %q cannot be used in this type specification: an exact type is required.", kw),
Subject: expr.Range().Ptr(),
}}
case "list", "map", "set":
return cty.DynamicPseudoType, hcl.Diagnostics{{
return cty.DynamicPseudoType, nil, hcl.Diagnostics{{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: fmt.Sprintf("The %s type constructor requires one argument specifying the element type.", kw),
Subject: expr.Range().Ptr(),
}}
case "object":
return cty.DynamicPseudoType, hcl.Diagnostics{{
return cty.DynamicPseudoType, nil, hcl.Diagnostics{{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: "The object type constructor requires one argument specifying the attribute types and values as a map.",
Subject: expr.Range().Ptr(),
}}
case "tuple":
return cty.DynamicPseudoType, hcl.Diagnostics{{
return cty.DynamicPseudoType, nil, hcl.Diagnostics{{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: "The tuple type constructor requires one argument specifying the element types as a list.",
@ -56,7 +63,7 @@ func getType(expr hcl.Expression, constraint bool) (cty.Type, hcl.Diagnostics) {
case "":
// okay! we'll fall through and try processing as a call, then.
default:
return cty.DynamicPseudoType, hcl.Diagnostics{{
return cty.DynamicPseudoType, nil, hcl.Diagnostics{{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: fmt.Sprintf("The keyword %q is not a valid type specification.", kw),
@ -68,7 +75,7 @@ func getType(expr hcl.Expression, constraint bool) (cty.Type, hcl.Diagnostics) {
// try to process it as a call instead.
call, diags := hcl.ExprCall(expr)
if diags.HasErrors() {
return cty.DynamicPseudoType, hcl.Diagnostics{{
return cty.DynamicPseudoType, nil, hcl.Diagnostics{{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: "A type specification is either a primitive type keyword (bool, number, string) or a complex type constructor call, like list(string).",
@ -77,13 +84,20 @@ func getType(expr hcl.Expression, constraint bool) (cty.Type, hcl.Diagnostics) {
}
switch call.Name {
case "bool", "string", "number", "any":
return cty.DynamicPseudoType, hcl.Diagnostics{{
case "bool", "string", "number":
return cty.DynamicPseudoType, nil, hcl.Diagnostics{{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: fmt.Sprintf("Primitive type keyword %q does not expect arguments.", call.Name),
Subject: &call.ArgsRange,
}}
case "any":
return cty.DynamicPseudoType, nil, hcl.Diagnostics{{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: fmt.Sprintf("Type constraint keyword %q does not expect arguments.", call.Name),
Subject: &call.ArgsRange,
}}
}
if len(call.Arguments) != 1 {
@ -98,7 +112,7 @@ func getType(expr hcl.Expression, constraint bool) (cty.Type, hcl.Diagnostics) {
switch call.Name {
case "list", "set", "map":
return cty.DynamicPseudoType, hcl.Diagnostics{{
return cty.DynamicPseudoType, nil, hcl.Diagnostics{{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: fmt.Sprintf("The %s type constructor requires one argument specifying the element type.", call.Name),
@ -106,7 +120,7 @@ func getType(expr hcl.Expression, constraint bool) (cty.Type, hcl.Diagnostics) {
Context: &contextRange,
}}
case "object":
return cty.DynamicPseudoType, hcl.Diagnostics{{
return cty.DynamicPseudoType, nil, hcl.Diagnostics{{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: "The object type constructor requires one argument specifying the attribute types and values as a map.",
@ -114,7 +128,7 @@ func getType(expr hcl.Expression, constraint bool) (cty.Type, hcl.Diagnostics) {
Context: &contextRange,
}}
case "tuple":
return cty.DynamicPseudoType, hcl.Diagnostics{{
return cty.DynamicPseudoType, nil, hcl.Diagnostics{{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: "The tuple type constructor requires one argument specifying the element types as a list.",
@ -127,18 +141,21 @@ func getType(expr hcl.Expression, constraint bool) (cty.Type, hcl.Diagnostics) {
switch call.Name {
case "list":
ety, diags := getType(call.Arguments[0], constraint)
return cty.List(ety), diags
ety, defaults, diags := getType(call.Arguments[0], constraint, withDefaults)
ty := cty.List(ety)
return ty, collectionDefaults(ty, defaults), diags
case "set":
ety, diags := getType(call.Arguments[0], constraint)
return cty.Set(ety), diags
ety, defaults, diags := getType(call.Arguments[0], constraint, withDefaults)
ty := cty.Set(ety)
return ty, collectionDefaults(ty, defaults), diags
case "map":
ety, diags := getType(call.Arguments[0], constraint)
return cty.Map(ety), diags
ety, defaults, diags := getType(call.Arguments[0], constraint, withDefaults)
ty := cty.Map(ety)
return ty, collectionDefaults(ty, defaults), diags
case "object":
attrDefs, diags := hcl.ExprMap(call.Arguments[0])
if diags.HasErrors() {
return cty.DynamicPseudoType, hcl.Diagnostics{{
return cty.DynamicPseudoType, nil, hcl.Diagnostics{{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: "Object type constructor requires a map whose keys are attribute names and whose values are the corresponding attribute types.",
@ -148,6 +165,9 @@ func getType(expr hcl.Expression, constraint bool) (cty.Type, hcl.Diagnostics) {
}
atys := make(map[string]cty.Type)
defaultValues := make(map[string]cty.Value)
children := make(map[string]*Defaults)
var optAttrs []string
for _, attrDef := range attrDefs {
attrName := hcl.ExprAsKeyword(attrDef.Key)
if attrName == "" {
@ -160,15 +180,116 @@ func getType(expr hcl.Expression, constraint bool) (cty.Type, hcl.Diagnostics) {
})
continue
}
aty, attrDiags := getType(attrDef.Value, constraint)
if _, exists := atys[attrName]; exists {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: "Object constructor map keys must be unique.",
Subject: attrDef.Key.Range().Ptr(),
Context: expr.Range().Ptr(),
})
continue
}
atyExpr := attrDef.Value
// the attribute type expression might be wrapped in the special
// modifier optional(...) to indicate an optional attribute. If
// so, we'll unwrap that first and make a note about it being
// optional for when we construct the type below.
var defaultExpr hcl.Expression
if call, callDiags := hcl.ExprCall(atyExpr); !callDiags.HasErrors() {
if call.Name == "optional" {
if len(call.Arguments) < 1 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: "Optional attribute modifier requires the attribute type as its argument.",
Subject: call.ArgsRange.Ptr(),
Context: atyExpr.Range().Ptr(),
})
continue
}
if constraint {
if withDefaults {
switch len(call.Arguments) {
case 2:
defaultExpr = call.Arguments[1]
defaultVal, defaultDiags := defaultExpr.Value(nil)
diags = append(diags, defaultDiags...)
if !defaultDiags.HasErrors() {
optAttrs = append(optAttrs, attrName)
defaultValues[attrName] = defaultVal
}
case 1:
optAttrs = append(optAttrs, attrName)
default:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: "Optional attribute modifier expects at most two arguments: the attribute type, and a default value.",
Subject: call.ArgsRange.Ptr(),
Context: atyExpr.Range().Ptr(),
})
}
} else {
if len(call.Arguments) == 1 {
optAttrs = append(optAttrs, attrName)
} else {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: "Optional attribute modifier expects only one argument: the attribute type.",
Subject: call.ArgsRange.Ptr(),
Context: atyExpr.Range().Ptr(),
})
}
}
} else {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: "Optional attribute modifier is only for type constraints, not for exact types.",
Subject: call.NameRange.Ptr(),
Context: atyExpr.Range().Ptr(),
})
}
atyExpr = call.Arguments[0]
}
}
aty, aDefaults, attrDiags := getType(atyExpr, constraint, withDefaults)
diags = append(diags, attrDiags...)
// If a default is set for an optional attribute, verify that it is
// convertible to the attribute type.
if defaultVal, ok := defaultValues[attrName]; ok {
convertedDefaultVal, err := convert.Convert(defaultVal, aty)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid default value for optional attribute",
Detail: fmt.Sprintf("This default value is not compatible with the attribute's type constraint: %s.", err),
Subject: defaultExpr.Range().Ptr(),
})
delete(defaultValues, attrName)
} else {
defaultValues[attrName] = convertedDefaultVal
}
}
atys[attrName] = aty
if aDefaults != nil {
children[attrName] = aDefaults
}
}
return cty.Object(atys), diags
ty := cty.ObjectWithOptionalAttrs(atys, optAttrs)
return ty, structuredDefaults(ty, defaultValues, children), diags
case "tuple":
elemDefs, diags := hcl.ExprList(call.Arguments[0])
if diags.HasErrors() {
return cty.DynamicPseudoType, hcl.Diagnostics{{
return cty.DynamicPseudoType, nil, hcl.Diagnostics{{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: "Tuple type constructor requires a list of element types.",
@ -177,16 +298,28 @@ func getType(expr hcl.Expression, constraint bool) (cty.Type, hcl.Diagnostics) {
}}
}
etys := make([]cty.Type, len(elemDefs))
children := make(map[string]*Defaults, len(elemDefs))
for i, defExpr := range elemDefs {
ety, elemDiags := getType(defExpr, constraint)
ety, elemDefaults, elemDiags := getType(defExpr, constraint, withDefaults)
diags = append(diags, elemDiags...)
etys[i] = ety
if elemDefaults != nil {
children[fmt.Sprintf("%d", i)] = elemDefaults
}
}
return cty.Tuple(etys), diags
ty := cty.Tuple(etys)
return ty, structuredDefaults(ty, nil, children), diags
case "optional":
return cty.DynamicPseudoType, nil, hcl.Diagnostics{{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: fmt.Sprintf("Keyword %q is valid only as a modifier for object type attributes.", call.Name),
Subject: call.NameRange.Ptr(),
}}
default:
// Can't access call.Arguments in this path because we've not validated
// that it contains exactly one expression here.
return cty.DynamicPseudoType, hcl.Diagnostics{{
return cty.DynamicPseudoType, nil, hcl.Diagnostics{{
Severity: hcl.DiagError,
Summary: invalidTypeSummary,
Detail: fmt.Sprintf("Keyword %q is not a valid type constructor.", call.Name),
@ -194,3 +327,33 @@ func getType(expr hcl.Expression, constraint bool) (cty.Type, hcl.Diagnostics) {
}}
}
}
func collectionDefaults(ty cty.Type, defaults *Defaults) *Defaults {
if defaults == nil {
return nil
}
return &Defaults{
Type: ty,
Children: map[string]*Defaults{
"": defaults,
},
}
}
func structuredDefaults(ty cty.Type, defaultValues map[string]cty.Value, children map[string]*Defaults) *Defaults {
if len(defaultValues) == 0 && len(children) == 0 {
return nil
}
defaults := &Defaults{
Type: ty,
}
if len(defaultValues) > 0 {
defaults.DefaultValues = defaultValues
}
if len(children) > 0 {
defaults.Children = children
}
return defaults
}

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package typeexpr
import (
@ -15,7 +18,8 @@ import (
// successful, returns the resulting type. If unsuccessful, error diagnostics
// are returned.
func Type(expr hcl.Expression) (cty.Type, hcl.Diagnostics) {
return getType(expr, false)
ty, _, diags := getType(expr, false, false)
return ty, diags
}
// TypeConstraint attempts to parse the given expression as a type constraint
@ -26,7 +30,16 @@ func Type(expr hcl.Expression) (cty.Type, hcl.Diagnostics) {
// allows the keyword "any" to represent cty.DynamicPseudoType, which is often
// used as a wildcard in type checking and type conversion operations.
func TypeConstraint(expr hcl.Expression) (cty.Type, hcl.Diagnostics) {
return getType(expr, true)
ty, _, diags := getType(expr, true, false)
return ty, diags
}
// TypeConstraintWithDefaults attempts to parse the given expression as a type
// constraint which may include default values for object attributes. If
// successful both the resulting type and corresponding defaults are returned.
// If unsuccessful, error diagnostics are returned.
func TypeConstraintWithDefaults(expr hcl.Expression) (cty.Type, *Defaults, hcl.Diagnostics) {
return getType(expr, true, true)
}
// TypeString returns a string rendering of the given type as it would be

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package typeexpr
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package gohcl
import (
@ -258,32 +261,14 @@ func decodeBodyToMap(body hcl.Body, ctx *hcl.EvalContext, v reflect.Value) hcl.D
}
func decodeBlockToValue(block *hcl.Block, ctx *hcl.EvalContext, v reflect.Value) hcl.Diagnostics {
var diags hcl.Diagnostics
diags := decodeBodyToValue(block.Body, ctx, v)
ty := v.Type()
switch {
case blockType.AssignableTo(ty):
v.Elem().Set(reflect.ValueOf(block))
case bodyType.AssignableTo(ty):
v.Elem().Set(reflect.ValueOf(block.Body))
case attrsType.AssignableTo(ty):
attrs, attrsDiags := block.Body.JustAttributes()
if len(attrsDiags) > 0 {
diags = append(diags, attrsDiags...)
if len(block.Labels) > 0 {
blockTags := getFieldTags(v.Type())
for li, lv := range block.Labels {
lfieldIdx := blockTags.Labels[li].FieldIndex
v.Field(lfieldIdx).Set(reflect.ValueOf(lv))
}
v.Elem().Set(reflect.ValueOf(attrs))
default:
diags = append(diags, decodeBodyToValue(block.Body, ctx, v)...)
if len(block.Labels) > 0 {
blockTags := getFieldTags(ty)
for li, lv := range block.Labels {
lfieldIdx := blockTags.Labels[li].FieldIndex
v.Field(lfieldIdx).Set(reflect.ValueOf(lv))
}
}
}
return diags

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Package gohcl allows decoding HCL configurations into Go data structures.
//
// It provides a convenient and concise way of describing the schema for
@ -24,11 +27,9 @@
// expression is assigned, or of any type accepted by gocty, in which case
// gocty will be used to assign the value to a native Go type.
//
// "block" fields may be of type *hcl.Block or hcl.Body, in which case the
// corresponding raw value is assigned, or may be a struct that recursively
// uses the same tags. Block fields may also be slices of any of these types,
// in which case multiple blocks of the corresponding type are decoded into
// the slice.
// "block" fields may be a struct that recursively uses the same tags, or a
// slice of such structs, in which case multiple blocks of the corresponding
// type are decoded into the slice.
//
// "body" can be placed on a single field of type hcl.Body to capture
// the full hcl.Body that was decoded for a block. This does not allow leftover

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package gohcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package gohcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package gohcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Package hclparse has the main API entry point for parsing both HCL native
// syntax and HCL JSON.
//

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Package hclsyntax contains the parser, AST, etc for HCL's native language,
// as opposed to the JSON variant.
//

View File

@ -1,7 +1,11 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (
"fmt"
"sort"
"sync"
"github.com/hashicorp/hcl/v2"
@ -25,7 +29,7 @@ type Expression interface {
}
// Assert that Expression implements hcl.Expression
var assertExprImplExpr hcl.Expression = Expression(nil)
var _ hcl.Expression = Expression(nil)
// ParenthesesExpr represents an expression written in grouping
// parentheses.
@ -269,6 +273,10 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
}
}
diagExtra := functionCallDiagExtra{
calledFunctionName: e.Name,
}
params := f.Params()
varParam := f.VarParam()
@ -296,6 +304,7 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
Context: e.Range().Ptr(),
Expression: expandExpr,
EvalContext: ctx,
Extra: &diagExtra,
})
return cty.DynamicVal, diags
}
@ -310,6 +319,7 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
Context: e.Range().Ptr(),
Expression: expandExpr,
EvalContext: ctx,
Extra: &diagExtra,
})
return cty.DynamicVal, diags
}
@ -341,6 +351,7 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
Context: e.Range().Ptr(),
Expression: expandExpr,
EvalContext: ctx,
Extra: &diagExtra,
})
return cty.DynamicVal, diags
}
@ -364,6 +375,7 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
Context: e.Range().Ptr(),
Expression: e,
EvalContext: ctx,
Extra: &diagExtra,
},
}
}
@ -381,6 +393,7 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
Context: e.Range().Ptr(),
Expression: e,
EvalContext: ctx,
Extra: &diagExtra,
},
}
}
@ -425,6 +438,7 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
Context: e.Range().Ptr(),
Expression: argExpr,
EvalContext: ctx,
Extra: &diagExtra,
})
}
}
@ -441,6 +455,10 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
resultVal, err := f.Call(argVals)
if err != nil {
// For errors in the underlying call itself we also return the raw
// call error via an extra method on our "diagnostic extra" value.
diagExtra.functionCallError = err
switch terr := err.(type) {
case function.ArgError:
i := terr.Index
@ -451,22 +469,57 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
param = varParam
}
// this can happen if an argument is (incorrectly) null.
if i > len(e.Args)-1 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid function argument",
Detail: fmt.Sprintf(
"Invalid value for %q parameter: %s.",
param.Name, err,
),
Subject: args[len(params)].StartRange().Ptr(),
Context: e.Range().Ptr(),
Expression: e,
EvalContext: ctx,
})
if param == nil || i > len(args)-1 {
// Getting here means that the function we called has a bug:
// it returned an arg error that refers to an argument index
// that wasn't present in the call. For that situation
// we'll degrade to a less specific error just to give
// some sort of answer, but best to still fix the buggy
// function so that it only returns argument indices that
// are in range.
switch {
case param != nil:
// In this case we'll assume that the function was trying
// to talk about a final variadic parameter but the caller
// didn't actually provide any arguments for it. That means
// we can at least still name the parameter in the
// error message, but our source range will be the call
// as a whole because we don't have an argument expression
// to highlight specifically.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid function argument",
Detail: fmt.Sprintf(
"Invalid value for %q parameter: %s.",
param.Name, err,
),
Subject: e.Range().Ptr(),
Expression: e,
EvalContext: ctx,
Extra: &diagExtra,
})
default:
// This is the most degenerate case of all, where the
// index is out of range even for the declared parameters,
// and so we can't tell which parameter the function is
// trying to report an error for. Just a generic error
// report in that case.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Error in function call",
Detail: fmt.Sprintf(
"Call to function %q failed: %s.",
e.Name, err,
),
Subject: e.StartRange().Ptr(),
Context: e.Range().Ptr(),
Expression: e,
EvalContext: ctx,
Extra: &diagExtra,
})
}
} else {
argExpr := e.Args[i]
argExpr := args[i]
// TODO: we should also unpick a PathError here and show the
// path to the deep value where the error was detected.
@ -481,6 +534,7 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
Context: e.Range().Ptr(),
Expression: argExpr,
EvalContext: ctx,
Extra: &diagExtra,
})
}
@ -496,6 +550,7 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
Context: e.Range().Ptr(),
Expression: e,
EvalContext: ctx,
Extra: &diagExtra,
})
}
@ -528,6 +583,39 @@ func (e *FunctionCallExpr) ExprCall() *hcl.StaticCall {
return ret
}
// FunctionCallDiagExtra is an interface implemented by the value in the "Extra"
// field of some diagnostics returned by FunctionCallExpr.Value, giving
// cooperating callers access to some machine-readable information about the
// call that a diagnostic relates to.
type FunctionCallDiagExtra interface {
// CalledFunctionName returns the name of the function being called at
// the time the diagnostic was generated, if any. Returns an empty string
// if there is no known called function.
CalledFunctionName() string
// FunctionCallError returns the error value returned by the implementation
// of the function being called, if any. Returns nil if the diagnostic was
// not returned in response to a call error.
//
// Some errors related to calling functions are generated by HCL itself
// rather than by the underlying function, in which case this method
// will return nil.
FunctionCallError() error
}
type functionCallDiagExtra struct {
calledFunctionName string
functionCallError error
}
func (e *functionCallDiagExtra) CalledFunctionName() string {
return e.calledFunctionName
}
func (e *functionCallDiagExtra) FunctionCallError() error {
return e.functionCallError
}
type ConditionalExpr struct {
Condition Expression
TrueResult Expression
@ -582,12 +670,8 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic
Severity: hcl.DiagError,
Summary: "Inconsistent conditional result types",
Detail: fmt.Sprintf(
// FIXME: Need a helper function for showing natural-language type diffs,
// since this will generate some useless messages in some cases, like
// "These expressions are object and object respectively" if the
// object types don't exactly match.
"The true and false result expressions must have consistent types. The given expressions are %s and %s, respectively.",
trueResult.Type().FriendlyName(), falseResult.Type().FriendlyName(),
"The true and false result expressions must have consistent types. %s.",
describeConditionalTypeMismatch(trueResult.Type(), falseResult.Type()),
),
Subject: hcl.RangeBetween(e.TrueResult.Range(), e.FalseResult.Range()).Ptr(),
Context: &e.SrcRange,
@ -612,14 +696,101 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic
return cty.UnknownVal(resultType), diags
}
if !condResult.IsKnown() {
return cty.UnknownVal(resultType), diags
// we use the unmarked values throughout the unknown branch
_, condResultMarks := condResult.Unmark()
trueResult, trueResultMarks := trueResult.Unmark()
falseResult, falseResultMarks := falseResult.Unmark()
// use a value to merge marks
_, resMarks := cty.DynamicVal.WithMarks(condResultMarks, trueResultMarks, falseResultMarks).Unmark()
trueRange := trueResult.Range()
falseRange := falseResult.Range()
// if both branches are known to be null, then the result must still be null
if trueResult.IsNull() && falseResult.IsNull() {
return cty.NullVal(resultType).WithMarks(resMarks), diags
}
// We might be able to offer a refined range for the result based on
// the two possible outcomes.
if trueResult.Type() == cty.Number && falseResult.Type() == cty.Number {
ref := cty.UnknownVal(cty.Number).Refine()
if trueRange.DefinitelyNotNull() && falseRange.DefinitelyNotNull() {
ref = ref.NotNull()
}
falseLo, falseLoInc := falseRange.NumberLowerBound()
falseHi, falseHiInc := falseRange.NumberUpperBound()
trueLo, trueLoInc := trueRange.NumberLowerBound()
trueHi, trueHiInc := trueRange.NumberUpperBound()
if falseLo.IsKnown() && trueLo.IsKnown() {
lo, loInc := falseLo, falseLoInc
switch {
case trueLo.LessThan(falseLo).True():
lo, loInc = trueLo, trueLoInc
case trueLo.Equals(falseLo).True():
loInc = trueLoInc || falseLoInc
}
ref = ref.NumberRangeLowerBound(lo, loInc)
}
if falseHi.IsKnown() && trueHi.IsKnown() {
hi, hiInc := falseHi, falseHiInc
switch {
case trueHi.GreaterThan(falseHi).True():
hi, hiInc = trueHi, trueHiInc
case trueHi.Equals(falseHi).True():
hiInc = trueHiInc || falseHiInc
}
ref = ref.NumberRangeUpperBound(hi, hiInc)
}
return ref.NewValue().WithMarks(resMarks), diags
}
if trueResult.Type().IsCollectionType() && falseResult.Type().IsCollectionType() {
if trueResult.Type().Equals(falseResult.Type()) {
ref := cty.UnknownVal(resultType).Refine()
if trueRange.DefinitelyNotNull() && falseRange.DefinitelyNotNull() {
ref = ref.NotNull()
}
falseLo := falseRange.LengthLowerBound()
falseHi := falseRange.LengthUpperBound()
trueLo := trueRange.LengthLowerBound()
trueHi := trueRange.LengthUpperBound()
lo := falseLo
if trueLo < falseLo {
lo = trueLo
}
hi := falseHi
if trueHi > falseHi {
hi = trueHi
}
ref = ref.CollectionLengthLowerBound(lo).CollectionLengthUpperBound(hi)
return ref.NewValue().WithMarks(resMarks), diags
}
}
ret := cty.UnknownVal(resultType)
if trueRange.DefinitelyNotNull() && falseRange.DefinitelyNotNull() {
ret = ret.RefineNotNull()
}
return ret.WithMarks(resMarks), diags
}
condResult, err := convert.Convert(condResult, cty.Bool)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Incorrect condition type",
Detail: fmt.Sprintf("The condition expression must be of type bool."),
Detail: "The condition expression must be of type bool.",
Subject: e.Condition.Range().Ptr(),
Context: &e.SrcRange,
Expression: e.Condition,
@ -679,6 +850,144 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic
}
}
// describeConditionalTypeMismatch makes a best effort to describe the
// difference between types in the true and false arms of a conditional
// expression in a way that would be useful to someone trying to understand
// why their conditional expression isn't valid.
//
// NOTE: This function is only designed to deal with situations
// where trueTy and falseTy are different. Calling it with two equal
// types will produce a nonsense result. This function also only really
// deals with situations that type unification can't resolve, so we should
// call this function only after trying type unification first.
func describeConditionalTypeMismatch(trueTy, falseTy cty.Type) string {
// The main tricky cases here are when both trueTy and falseTy are
// of the same structural type kind, such as both being object types
// or both being tuple types. In that case the "FriendlyName" method
// returns only "object" or "tuple" and so we need to do some more
// work to describe what's different inside them.
switch {
case trueTy.IsObjectType() && falseTy.IsObjectType():
// We'll first gather up the attribute names and sort them. In the
// event that there are multiple attributes that disagree across
// the two types, we'll prefer to report the one that sorts lexically
// least just so that our error message is consistent between
// evaluations.
var trueAttrs, falseAttrs []string
for name := range trueTy.AttributeTypes() {
trueAttrs = append(trueAttrs, name)
}
sort.Strings(trueAttrs)
for name := range falseTy.AttributeTypes() {
falseAttrs = append(falseAttrs, name)
}
sort.Strings(falseAttrs)
for _, name := range trueAttrs {
if !falseTy.HasAttribute(name) {
return fmt.Sprintf("The 'true' value includes object attribute %q, which is absent in the 'false' value", name)
}
trueAty := trueTy.AttributeType(name)
falseAty := falseTy.AttributeType(name)
if !trueAty.Equals(falseAty) {
// For deeply-nested differences this will likely get very
// clunky quickly by nesting these messages inside one another,
// but we'll accept that for now in the interests of producing
// _some_ useful feedback, even if it isn't as concise as
// we'd prefer it to be. Deeply-nested structures in
// conditionals are thankfully not super common.
return fmt.Sprintf(
"Type mismatch for object attribute %q: %s",
name, describeConditionalTypeMismatch(trueAty, falseAty),
)
}
}
for _, name := range falseAttrs {
if !trueTy.HasAttribute(name) {
return fmt.Sprintf("The 'false' value includes object attribute %q, which is absent in the 'true' value", name)
}
// NOTE: We don't need to check the attribute types again, because
// any attribute that both types have in common would already have
// been checked in the previous loop.
}
case trueTy.IsTupleType() && falseTy.IsTupleType():
trueEtys := trueTy.TupleElementTypes()
falseEtys := falseTy.TupleElementTypes()
if trueCount, falseCount := len(trueEtys), len(falseEtys); trueCount != falseCount {
return fmt.Sprintf("The 'true' tuple has length %d, but the 'false' tuple has length %d", trueCount, falseCount)
}
// NOTE: Thanks to the condition above, we know that both tuples are
// of the same length and so they must have some differing types
// instead.
for i := range trueEtys {
trueEty := trueEtys[i]
falseEty := falseEtys[i]
if !trueEty.Equals(falseEty) {
// For deeply-nested differences this will likely get very
// clunky quickly by nesting these messages inside one another,
// but we'll accept that for now in the interests of producing
// _some_ useful feedback, even if it isn't as concise as
// we'd prefer it to be. Deeply-nested structures in
// conditionals are thankfully not super common.
return fmt.Sprintf(
"Type mismatch for tuple element %d: %s",
i, describeConditionalTypeMismatch(trueEty, falseEty),
)
}
}
case trueTy.IsCollectionType() && falseTy.IsCollectionType():
// For this case we're specifically interested in the situation where:
// - both collections are of the same kind, AND
// - the element types of both are either object or tuple types.
// This is just to avoid writing a useless statement like
// "The 'true' value is list of object, but the 'false' value is list of object".
// This still doesn't account for more awkward cases like collections
// of collections of structural types, but we won't let perfect be
// the enemy of the good.
trueEty := trueTy.ElementType()
falseEty := falseTy.ElementType()
if (trueTy.IsListType() && falseTy.IsListType()) || (trueTy.IsMapType() && falseTy.IsMapType()) || (trueTy.IsSetType() && falseTy.IsSetType()) {
if (trueEty.IsObjectType() && falseEty.IsObjectType()) || (trueEty.IsTupleType() && falseEty.IsTupleType()) {
noun := "collection"
switch { // NOTE: We now know that trueTy and falseTy have the same collection kind
case trueTy.IsListType():
noun = "list"
case trueTy.IsSetType():
noun = "set"
case trueTy.IsMapType():
noun = "map"
}
return fmt.Sprintf(
"Mismatched %s element types: %s",
noun, describeConditionalTypeMismatch(trueEty, falseEty),
)
}
}
}
// If we don't manage any more specialized message, we'll just report
// what the two types are.
trueName := trueTy.FriendlyName()
falseName := falseTy.FriendlyName()
if trueName == falseName {
// Absolute last resort for when we have no special rule above but
// we have two types with the same friendly name anyway. This is
// the most vague of all possible messages but is reserved for
// particularly awkward cases, like lists of lists of differing tuple
// types.
return "At least one deeply-nested attribute or element is not compatible across both the 'true' and the 'false' value"
}
return fmt.Sprintf(
"The 'true' value is %s, but the 'false' value is %s",
trueTy.FriendlyName(), falseTy.FriendlyName(),
)
}
func (e *ConditionalExpr) Range() hcl.Range {
return e.SrcRange
}
@ -966,9 +1275,9 @@ func (e *ObjectConsKeyExpr) UnwrapExpression() Expression {
// ForExpr represents iteration constructs:
//
// tuple = [for i, v in list: upper(v) if i > 2]
// object = {for k, v in map: k => upper(v)}
// object_of_tuples = {for v in list: v.key: v...}
// tuple = [for i, v in list: upper(v) if i > 2]
// object = {for k, v in map: k => upper(v)}
// object_of_tuples = {for v in list: v.key: v...}
type ForExpr struct {
KeyVar string // empty if ignoring the key
ValVar string
@ -1399,7 +1708,24 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
return cty.DynamicVal, diags
}
upgradedUnknown := false
if autoUpgrade {
// If we're upgrading an unknown value to a tuple/list, the result
// cannot be known. Otherwise a tuple containing an unknown value will
// upgrade to a different number of elements depending on whether
// sourceVal becomes null or not.
// We record this condition here so we can process any remaining
// expression after the * to verify the result of the traversal. For
// example, it is valid to use a splat on a single object to retrieve a
// list of a single attribute, but we still need to check if that
// attribute actually exists.
if !sourceVal.IsKnown() {
sourceRng := sourceVal.Range()
if sourceRng.CouldBeNull() {
upgradedUnknown = true
}
}
sourceVal = cty.TupleVal([]cty.Value{sourceVal})
sourceTy = sourceVal.Type()
}
@ -1440,7 +1766,21 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
// checking to proceed.
ty, tyDiags := resultTy()
diags = append(diags, tyDiags...)
return cty.UnknownVal(ty), diags
ret := cty.UnknownVal(ty)
if ty != cty.DynamicPseudoType {
ret = ret.RefineNotNull()
}
if ty.IsListType() && sourceVal.Type().IsCollectionType() {
// We can refine the length of an unknown list result based on
// the source collection's own length.
sv, _ := sourceVal.Unmark()
sourceRng := sv.Range()
ret = ret.Refine().
CollectionLengthLowerBound(sourceRng.LengthLowerBound()).
CollectionLengthUpperBound(sourceRng.LengthUpperBound()).
NewValue()
}
return ret.WithSameMarks(sourceVal), diags
}
// Unmark the collection, and save the marks to apply to the returned
@ -1466,6 +1806,10 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
}
e.Item.clearValue(ctx) // clean up our temporary value
if upgradedUnknown {
return cty.DynamicVal, diags
}
if !isKnown {
// We'll ingore the resultTy diagnostics in this case since they
// will just be the same errors we saw while iterating above.

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (
@ -35,11 +38,9 @@ func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
if partVal.IsNull() {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid template interpolation value",
Detail: fmt.Sprintf(
"The expression result is null. Cannot include a null value in a string template.",
),
Severity: hcl.DiagError,
Summary: "Invalid template interpolation value",
Detail: "The expression result is null. Cannot include a null value in a string template.",
Subject: part.Range().Ptr(),
Context: &e.SrcRange,
Expression: part,
@ -48,6 +49,12 @@ func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
continue
}
// Unmark the part and merge its marks into the set
unmarkedVal, partMarks := partVal.Unmark()
for k, v := range partMarks {
marks[k] = v
}
if !partVal.IsKnown() {
// If any part is unknown then the result as a whole must be
// unknown too. We'll keep on processing the rest of the parts
@ -57,7 +64,7 @@ func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
continue
}
strVal, err := convert.Convert(partVal, cty.String)
strVal, err := convert.Convert(unmarkedVal, cty.String)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
@ -74,22 +81,38 @@ func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
continue
}
// Unmark the part and merge its marks into the set
unmarked, partMarks := strVal.Unmark()
for k, v := range partMarks {
marks[k] = v
// If we're just continuing to validate after we found an unknown value
// then we'll skip appending so that "buf" will contain only the
// known prefix of the result.
if isKnown && !diags.HasErrors() {
buf.WriteString(strVal.AsString())
}
buf.WriteString(unmarked.AsString())
}
var ret cty.Value
if !isKnown {
ret = cty.UnknownVal(cty.String)
if !diags.HasErrors() { // Invalid input means our partial result buffer is suspect
if knownPrefix := buf.String(); knownPrefix != "" {
byteLen := len(knownPrefix)
// Impose a reasonable upper limit to avoid producing too long a prefix.
// The 128 B is about 10% of the safety limits in cty's msgpack decoder.
// @see https://github.com/zclconf/go-cty/blob/v1.13.2/cty/msgpack/unknown.go#L170-L175
//
// This operation is safe because StringPrefix removes incomplete trailing grapheme clusters.
if byteLen > 128 { // arbitrarily-decided threshold
byteLen = 128
}
ret = ret.Refine().StringPrefix(knownPrefix[:byteLen]).NewValue()
}
}
} else {
ret = cty.StringVal(buf.String())
}
// A template rendering result is never null.
ret = ret.RefineNotNull()
// Apply the full set of marks to the returned value
return ret.WithMarks(marks), diags
}

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
// Generated by expression_vars_get.go. DO NOT EDIT.

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
//go:generate go run expression_vars_gen.go

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (
@ -6,7 +9,7 @@ import (
"strconv"
"unicode/utf8"
"github.com/apparentlymart/go-textseg/v12/textseg"
"github.com/apparentlymart/go-textseg/v15/textseg"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
)
@ -76,14 +79,37 @@ Token:
default:
bad := p.Read()
if !p.recovery {
if bad.Type == TokenOQuote {
switch bad.Type {
case TokenOQuote:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid argument name",
Detail: "Argument names must not be quoted.",
Subject: &bad.Range,
})
} else {
case TokenEOF:
switch end {
case TokenCBrace:
// If we're looking for a closing brace then we're parsing a block
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unclosed configuration block",
Detail: "There is no closing brace for this block before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.",
Subject: &startRange,
})
default:
// The only other "end" should itself be TokenEOF (for
// the top-level body) and so we shouldn't get here,
// but we'll return a generic error message anyway to
// be resilient.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unclosed configuration body",
Detail: "Found end of file before the end of this configuration body.",
Subject: &startRange,
})
}
default:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Argument or block definition required",
@ -144,8 +170,6 @@ func (p *parser) ParseBodyItem() (Node, hcl.Diagnostics) {
},
}
}
return nil, nil
}
// parseSingleAttrBody is a weird variant of ParseBody that deals with the
@ -388,12 +412,23 @@ Token:
// user intent for this one, we'll skip it if we're already in
// recovery mode.
if !p.recovery {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid single-argument block definition",
Detail: "A single-line block definition must end with a closing brace immediately after its single argument definition.",
Subject: p.Peek().Range.Ptr(),
})
switch p.Peek().Type {
case TokenEOF:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unclosed configuration block",
Detail: "There is no closing brace for this block before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.",
Subject: oBrace.Range.Ptr(),
Context: hcl.RangeBetween(ident.Range, oBrace.Range).Ptr(),
})
default:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid single-argument block definition",
Detail: "A single-line block definition must end with a closing brace immediately after its single argument definition.",
Subject: p.Peek().Range.Ptr(),
})
}
}
p.recover(TokenCBrace)
}
@ -1059,12 +1094,22 @@ func (p *parser) parseExpressionTerm() (Expression, hcl.Diagnostics) {
default:
var diags hcl.Diagnostics
if !p.recovery {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid expression",
Detail: "Expected the start of an expression, but found an invalid expression token.",
Subject: &start.Range,
})
switch start.Type {
case TokenEOF:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing expression",
Detail: "Expected the start of an expression, but found the end of the file.",
Subject: &start.Range,
})
default:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid expression",
Detail: "Expected the start of an expression, but found an invalid expression token.",
Subject: &start.Range,
})
}
}
p.setRecovery()
@ -1132,7 +1177,12 @@ Token:
// if there was a parse error in the argument then we've
// probably been left in a weird place in the token stream,
// so we'll bail out with a partial argument list.
p.recover(TokenCParen)
recoveredTok := p.recover(TokenCParen)
// record the recovered token, if one was found
if recoveredTok.Type == TokenCParen {
closeTok = recoveredTok
}
break Token
}
@ -1163,13 +1213,23 @@ Token:
}
if sep.Type != TokenComma {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing argument separator",
Detail: "A comma is required to separate each function argument from the next.",
Subject: &sep.Range,
Context: hcl.RangeBetween(name.Range, sep.Range).Ptr(),
})
switch sep.Type {
case TokenEOF:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unterminated function call",
Detail: "There is no closing parenthesis for this function call before the end of the file. This may be caused by incorrect parethesis nesting elsewhere in this file.",
Subject: hcl.RangeBetween(name.Range, openTok.Range).Ptr(),
})
default:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing argument separator",
Detail: "A comma is required to separate each function argument from the next.",
Subject: &sep.Range,
Context: hcl.RangeBetween(name.Range, sep.Range).Ptr(),
})
}
closeTok = p.recover(TokenCParen)
break Token
}
@ -1242,13 +1302,23 @@ func (p *parser) parseTupleCons() (Expression, hcl.Diagnostics) {
if next.Type != TokenComma {
if !p.recovery {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing item separator",
Detail: "Expected a comma to mark the beginning of the next item.",
Subject: &next.Range,
Context: hcl.RangeBetween(open.Range, next.Range).Ptr(),
})
switch next.Type {
case TokenEOF:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unterminated tuple constructor expression",
Detail: "There is no corresponding closing bracket before the end of the file. This may be caused by incorrect bracket nesting elsewhere in this file.",
Subject: open.Range.Ptr(),
})
default:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing item separator",
Detail: "Expected a comma to mark the beginning of the next item.",
Subject: &next.Range,
Context: hcl.RangeBetween(open.Range, next.Range).Ptr(),
})
}
}
close = p.recover(TokenCBrack)
break
@ -1359,6 +1429,13 @@ func (p *parser) parseObjectCons() (Expression, hcl.Diagnostics) {
Subject: &next.Range,
Context: hcl.RangeBetween(open.Range, next.Range).Ptr(),
})
case TokenEOF:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unterminated object constructor expression",
Detail: "There is no corresponding closing brace before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.",
Subject: open.Range.Ptr(),
})
default:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
@ -1399,13 +1476,23 @@ func (p *parser) parseObjectCons() (Expression, hcl.Diagnostics) {
if next.Type != TokenComma && next.Type != TokenNewline {
if !p.recovery {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing attribute separator",
Detail: "Expected a newline or comma to mark the beginning of the next attribute.",
Subject: &next.Range,
Context: hcl.RangeBetween(open.Range, next.Range).Ptr(),
})
switch next.Type {
case TokenEOF:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unterminated object constructor expression",
Detail: "There is no corresponding closing brace before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.",
Subject: open.Range.Ptr(),
})
default:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing attribute separator",
Detail: "Expected a newline or comma to mark the beginning of the next attribute.",
Subject: &next.Range,
Context: hcl.RangeBetween(open.Range, next.Range).Ptr(),
})
}
}
close = p.recover(TokenCBrace)
break
@ -1661,7 +1748,7 @@ func (p *parser) parseQuotedStringLiteral() (string, hcl.Range, hcl.Diagnostics)
var diags hcl.Diagnostics
ret := &bytes.Buffer{}
var cQuote Token
var endRange hcl.Range
Token:
for {
@ -1669,7 +1756,7 @@ Token:
switch tok.Type {
case TokenCQuote:
cQuote = tok
endRange = tok.Range
break Token
case TokenQuotedLit:
@ -1712,6 +1799,7 @@ Token:
Subject: &tok.Range,
Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(),
})
endRange = tok.Range
break Token
default:
@ -1724,13 +1812,14 @@ Token:
Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(),
})
p.recover(TokenCQuote)
endRange = tok.Range
break Token
}
}
return ret.String(), hcl.RangeBetween(oQuote.Range, cQuote.Range), diags
return ret.String(), hcl.RangeBetween(oQuote.Range, endRange), diags
}
// ParseStringLiteralToken processes the given token, which must be either a

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (
@ -5,7 +8,7 @@ import (
"strings"
"unicode"
"github.com/apparentlymart/go-textseg/v12/textseg"
"github.com/apparentlymart/go-textseg/v15/textseg"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
)
@ -38,6 +41,7 @@ func (p *parser) parseTemplateInner(end TokenType, flushHeredoc bool) ([]Express
if flushHeredoc {
flushHeredocTemplateParts(parts) // Trim off leading spaces on lines per the flush heredoc spec
}
meldConsecutiveStringLiterals(parts)
tp := templateParser{
Tokens: parts.Tokens,
SrcRange: parts.SrcRange,
@ -413,13 +417,44 @@ Token:
close := p.Peek()
if close.Type != TokenTemplateSeqEnd {
if !p.recovery {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Extra characters after interpolation expression",
Detail: "Expected a closing brace to end the interpolation expression, but found extra characters.",
Subject: &close.Range,
Context: hcl.RangeBetween(startRange, close.Range).Ptr(),
})
switch close.Type {
case TokenEOF:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unclosed template interpolation sequence",
Detail: "There is no closing brace for this interpolation sequence before the end of the file. This might be caused by incorrect nesting inside the given expression.",
Subject: &startRange,
})
case TokenColon:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Extra characters after interpolation expression",
Detail: "Template interpolation doesn't expect a colon at this location. Did you intend this to be a literal sequence to be processed as part of another language? If so, you can escape it by starting with \"$${\" instead of just \"${\".",
Subject: &close.Range,
Context: hcl.RangeBetween(startRange, close.Range).Ptr(),
})
default:
if (close.Type == TokenCQuote || close.Type == TokenOQuote) && end == TokenCQuote {
// We'll get here if we're processing a _quoted_
// template and we find an errant quote inside an
// interpolation sequence, which suggests that
// the interpolation sequence is missing its terminator.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unclosed template interpolation sequence",
Detail: "There is no closing brace for this interpolation sequence before the end of the quoted template. This might be caused by incorrect nesting inside the given expression.",
Subject: &startRange,
})
} else {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Extra characters after interpolation expression",
Detail: "Expected a closing brace to end the interpolation expression, but found extra characters.\n\nThis can happen when you include interpolation syntax for another language, such as shell scripting, but forget to escape the interpolation start token. If this is an embedded sequence for another language, escape it by starting with \"$${\" instead of just \"${\".",
Subject: &close.Range,
Context: hcl.RangeBetween(startRange, close.Range).Ptr(),
})
}
}
}
p.recover(TokenTemplateSeqEnd)
} else {
@ -720,6 +755,37 @@ func flushHeredocTemplateParts(parts *templateParts) {
}
}
// meldConsecutiveStringLiterals simplifies the AST output by combining a
// sequence of string literal tokens into a single string literal. This must be
// performed after any whitespace trimming operations.
func meldConsecutiveStringLiterals(parts *templateParts) {
if len(parts.Tokens) == 0 {
return
}
// Loop over all tokens starting at the second element, as we want to join
// pairs of consecutive string literals.
i := 1
for i < len(parts.Tokens) {
if prevLiteral, ok := parts.Tokens[i-1].(*templateLiteralToken); ok {
if literal, ok := parts.Tokens[i].(*templateLiteralToken); ok {
// The current and previous tokens are both literals: combine
prevLiteral.Val = prevLiteral.Val + literal.Val
prevLiteral.SrcRange.End = literal.SrcRange.End
// Remove the current token from the slice
parts.Tokens = append(parts.Tokens[:i], parts.Tokens[i+1:]...)
// Continue without moving forward in the slice
continue
}
}
// Try the next pair of tokens
i++
}
}
type templateParts struct {
Tokens []templateToken
SrcRange hcl.Range

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//line scan_string_lit.rl:1
package hclsyntax

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//line scan_tokens.rl:1
package hclsyntax

View File

@ -82,9 +82,9 @@ Comments serve as program documentation and come in two forms:
- _Inline comments_ start with the `/*` sequence and end with the `*/`
sequence, and may have any characters within except the ending sequence.
An inline comments is considered equivalent to a whitespace sequence.
An inline comment is considered equivalent to a whitespace sequence.
Comments and whitespace cannot begin within within other comments, or within
Comments and whitespace cannot begin within other comments, or within
template literals except inside an interpolation sequence or template directive.
### Identifiers
@ -268,10 +268,10 @@ collection value.
```ebnf
CollectionValue = tuple | object;
tuple = "[" (
(Expression ("," Expression)* ","?)?
(Expression (("," | Newline) Expression)* ","?)?
) "]";
object = "{" (
(objectelem ("," objectelem)* ","?)?
(objectelem (( "," | Newline) objectelem)* ","?)?
) "}";
objectelem = (Identifier | Expression) ("=" | ":") Expression;
```
@ -293,18 +293,20 @@ Between the open and closing delimiters of these sequences, newline sequences
are ignored as whitespace.
There is a syntax ambiguity between _for expressions_ and collection values
whose first element is a reference to a variable named `for`. The
_for expression_ interpretation has priority, so to produce a tuple whose
first element is the value of a variable named `for`, or an object with a
key named `for`, use parentheses to disambiguate:
whose first element starts with an identifier named `for`. The _for expression_
interpretation has priority, so to write a key literally named `for`
or an expression derived from a variable named `for` you must use parentheses
or quotes to disambiguate:
- `[for, foo, baz]` is a syntax error.
- `[(for), foo, baz]` is a tuple whose first element is the value of variable
`for`.
- `{for: 1, baz: 2}` is a syntax error.
- `{(for): 1, baz: 2}` is an object with an attribute literally named `for`.
- `{baz: 2, for: 1}` is equivalent to the previous example, and resolves the
- `{for = 1, baz = 2}` is a syntax error.
- `{"for" = 1, baz = 2}` is an object with an attribute literally named `for`.
- `{baz = 2, for = 1}` is equivalent to the previous example, and resolves the
ambiguity by reordering.
- `{(for) = 1, baz = 2}` is an object with a key with the same value as the
variable `for`.
### Template Expressions
@ -489,7 +491,7 @@ that were produced against each distinct key.
- `[for v in ["a", "b"]: v]` returns `["a", "b"]`.
- `[for i, v in ["a", "b"]: i]` returns `[0, 1]`.
- `{for i, v in ["a", "b"]: v => i}` returns `{a = 0, b = 1}`.
- `{for i, v in ["a", "a", "b"]: k => v}` produces an error, because attribute
- `{for i, v in ["a", "a", "b"]: v => i}` produces an error, because attribute
`a` is defined twice.
- `{for i, v in ["a", "a", "b"]: v => i...}` returns `{a = [0, 1], b = [2]}`.
@ -633,7 +635,7 @@ binaryOp = ExprTerm binaryOperator ExprTerm;
binaryOperator = compareOperator | arithmeticOperator | logicOperator;
compareOperator = "==" | "!=" | "<" | ">" | "<=" | ">=";
arithmeticOperator = "+" | "-" | "*" | "/" | "%";
logicOperator = "&&" | "||" | "!";
logicOperator = "&&" | "||";
```
The unary operators have the highest precedence.
@ -888,7 +890,7 @@ as templates.
- `hello ${true}` produces the string `"hello true"`
- `${""}${true}` produces the string `"true"` because there are two
interpolation sequences, even though one produces an empty result.
- `%{ for v in [true] }${v}%{ endif }` produces the string `true` because
- `%{ for v in [true] }${v}%{ endfor }` produces the string `true` because
the presence of the `for` directive circumvents the unwrapping even though
the final result is a single value.

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (
@ -13,17 +16,12 @@ func (b *Block) AsHCLBlock() *hcl.Block {
return nil
}
lastHeaderRange := b.TypeRange
if len(b.LabelRanges) > 0 {
lastHeaderRange = b.LabelRanges[len(b.LabelRanges)-1]
}
return &hcl.Block{
Type: b.Type,
Labels: b.Labels,
Body: b.Body,
DefRange: hcl.RangeBetween(b.TypeRange, lastHeaderRange),
DefRange: b.DefRange(),
TypeRange: b.TypeRange,
LabelRanges: b.LabelRanges,
}
@ -40,7 +38,7 @@ type Body struct {
hiddenBlocks map[string]struct{}
SrcRange hcl.Range
EndRange hcl.Range // Final token of the body, for reporting missing items
EndRange hcl.Range // Final token of the body (zero-length range)
}
// Assert that *Body implements hcl.Body
@ -390,5 +388,9 @@ func (b *Block) Range() hcl.Range {
}
func (b *Block) DefRange() hcl.Range {
return hcl.RangeBetween(b.TypeRange, b.OpenBraceRange)
lastHeaderRange := b.TypeRange
if len(b.LabelRanges) > 0 {
lastHeaderRange = b.LabelRanges[len(b.LabelRanges)-1]
}
return hcl.RangeBetween(b.TypeRange, lastHeaderRange)
}

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,10 +1,13 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (
"bytes"
"fmt"
"github.com/apparentlymart/go-textseg/v12/textseg"
"github.com/apparentlymart/go-textseg/v15/textseg"
"github.com/hashicorp/hcl/v2"
)
@ -191,8 +194,10 @@ func checkInvalidTokens(tokens Tokens) hcl.Diagnostics {
toldBadUTF8 := 0
for _, tok := range tokens {
// copy token so it's safe to point to it
tok := tok
tokRange := func() *hcl.Range {
r := tok.Range
return &r
}
switch tok.Type {
case TokenBitwiseAnd, TokenBitwiseOr, TokenBitwiseXor, TokenBitwiseNot:
@ -211,7 +216,7 @@ func checkInvalidTokens(tokens Tokens) hcl.Diagnostics {
Severity: hcl.DiagError,
Summary: "Unsupported operator",
Detail: fmt.Sprintf("Bitwise operators are not supported.%s", suggestion),
Subject: &tok.Range,
Subject: tokRange(),
})
toldBitwise++
}
@ -221,7 +226,7 @@ func checkInvalidTokens(tokens Tokens) hcl.Diagnostics {
Severity: hcl.DiagError,
Summary: "Unsupported operator",
Detail: "\"**\" is not a supported operator. Exponentiation is not supported as an operator.",
Subject: &tok.Range,
Subject: tokRange(),
})
toldExponent++
@ -234,7 +239,7 @@ func checkInvalidTokens(tokens Tokens) hcl.Diagnostics {
Severity: hcl.DiagError,
Summary: "Invalid character",
Detail: "The \"`\" character is not valid. To create a multi-line string, use the \"heredoc\" syntax, like \"<<EOT\".",
Subject: &tok.Range,
Subject: tokRange(),
})
}
if toldBacktick <= 2 {
@ -246,7 +251,7 @@ func checkInvalidTokens(tokens Tokens) hcl.Diagnostics {
Severity: hcl.DiagError,
Summary: "Invalid character",
Detail: "Single quotes are not valid. Use double quotes (\") to enclose strings.",
Subject: &tok.Range,
Subject: tokRange(),
}
diags = append(diags, newDiag)
}
@ -259,7 +264,7 @@ func checkInvalidTokens(tokens Tokens) hcl.Diagnostics {
Severity: hcl.DiagError,
Summary: "Invalid character",
Detail: "The \";\" character is not valid. Use newlines to separate arguments and blocks, and commas to separate items in collection values.",
Subject: &tok.Range,
Subject: tokRange(),
})
toldSemicolon++
@ -270,7 +275,7 @@ func checkInvalidTokens(tokens Tokens) hcl.Diagnostics {
Severity: hcl.DiagError,
Summary: "Invalid character",
Detail: "Tab characters may not be used. The recommended indentation style is two spaces per indent.",
Subject: &tok.Range,
Subject: tokRange(),
})
toldTabs++
@ -281,7 +286,7 @@ func checkInvalidTokens(tokens Tokens) hcl.Diagnostics {
Severity: hcl.DiagError,
Summary: "Invalid character encoding",
Detail: "All input files must be UTF-8 encoded. Ensure that UTF-8 encoding is selected in your editor.",
Subject: &tok.Range,
Subject: tokRange(),
})
toldBadUTF8++
@ -291,7 +296,7 @@ func checkInvalidTokens(tokens Tokens) hcl.Diagnostics {
Severity: hcl.DiagError,
Summary: "Invalid multi-line string",
Detail: "Quoted strings may not be split over multiple lines. To produce a multi-line string, either use the \\n escape to represent a newline character or use the \"heredoc\" multi-line template syntax.",
Subject: &tok.Range,
Subject: tokRange(),
})
case TokenInvalid:
chars := string(tok.Bytes)
@ -301,14 +306,14 @@ func checkInvalidTokens(tokens Tokens) hcl.Diagnostics {
Severity: hcl.DiagError,
Summary: "Invalid character",
Detail: "\"Curly quotes\" are not valid here. These can sometimes be inadvertently introduced when sharing code via documents or discussion forums. It might help to replace the character with a \"straight quote\".",
Subject: &tok.Range,
Subject: tokRange(),
})
default:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid character",
Detail: "This character is not used within the language.",
Subject: &tok.Range,
Subject: tokRange(),
})
}
}

View File

@ -1,4 +1,7 @@
#!/usr/bin/env ruby
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
#
# This scripted has been updated to accept more command-line arguments:
#

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclwrite
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclwrite
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclwrite
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclwrite
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclwrite
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Package hclwrite deals with the problem of generating HCL configuration
// and of making specific surgical changes to existing HCL configurations.
//

View File

@ -1,19 +1,12 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclwrite
import (
"github.com/hashicorp/hcl/v2/hclsyntax"
)
var inKeyword = hclsyntax.Keyword([]byte{'i', 'n'})
// placeholder token used when we don't have a token but we don't want
// to pass a real "nil" and complicate things with nil pointer checks
var nilToken = &Token{
Type: hclsyntax.TokenNil,
Bytes: []byte{},
SpacesBefore: 0,
}
// format rewrites tokens within the given sequence, in-place, to adjust the
// whitespace around their content to achieve canonical formatting.
func format(tokens Tokens) {
@ -108,6 +101,14 @@ func formatIndent(lines []formatLine) {
}
func formatSpaces(lines []formatLine) {
// placeholder token used when we don't have a token but we don't want
// to pass a real "nil" and complicate things with nil pointer checks
nilToken := &Token{
Type: hclsyntax.TokenNil,
Bytes: []byte{},
SpacesBefore: 0,
}
for _, line := range lines {
for i, token := range line.lead {
var before, after *Token
@ -119,7 +120,7 @@ func formatSpaces(lines []formatLine) {
if i < (len(line.lead) - 1) {
after = line.lead[i+1]
} else {
after = nilToken
continue
}
if spaceAfterToken(token, before, after) {
after.SpacesBefore = 1
@ -143,7 +144,7 @@ func formatSpaces(lines []formatLine) {
if i < (len(line.assign) - 1) {
after = line.assign[i+1]
} else {
after = nilToken
continue
}
if spaceAfterToken(token, before, after) {
after.SpacesBefore = 1
@ -156,7 +157,6 @@ func formatSpaces(lines []formatLine) {
}
func formatCells(lines []formatLine) {
chainStart := -1
maxColumns := 0
@ -218,7 +218,6 @@ func formatCells(lines []formatLine) {
if chainStart != -1 {
closeCommentChain(len(lines))
}
}
// spaceAfterToken decides whether a particular subject token should have a
@ -251,7 +250,7 @@ func spaceAfterToken(subject, before, after *Token) bool {
// No extra spaces within templates
return false
case inKeyword.TokenMatches(subject.asHCLSyntax()) && before.Type == hclsyntax.TokenIdent:
case hclsyntax.Keyword([]byte{'i', 'n'}).TokenMatches(subject.asHCLSyntax()) && before.Type == hclsyntax.TokenIdent:
// This is a special case for inside for expressions where a user
// might want to use a literal tuple constructor:
// [for x in [foo]: x]

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclwrite
import (
@ -39,6 +42,146 @@ func TokensForTraversal(traversal hcl.Traversal) Tokens {
return toks
}
// TokensForIdentifier returns a sequence of tokens representing just the
// given identifier.
//
// In practice this function can only ever generate exactly one token, because
// an identifier is always a leaf token in the syntax tree.
//
// This is similar to calling TokensForTraversal with a single-step absolute
// traversal, but avoids the need to construct a separate traversal object
// for this simple common case. If you need to generate a multi-step traversal,
// use TokensForTraversal instead.
func TokensForIdentifier(name string) Tokens {
return Tokens{
newIdentToken(name),
}
}
// TokensForTuple returns a sequence of tokens that represents a tuple
// constructor, with element expressions populated from the given list
// of tokens.
//
// TokensForTuple includes the given elements verbatim into the element
// positions in the resulting tuple expression, without any validation to
// ensure that they represent valid expressions. Use TokensForValue or
// TokensForTraversal to generate valid leaf expression values, or use
// TokensForTuple, TokensForObject, and TokensForFunctionCall to
// generate other nested compound expressions.
func TokensForTuple(elems []Tokens) Tokens {
var toks Tokens
toks = append(toks, &Token{
Type: hclsyntax.TokenOBrack,
Bytes: []byte{'['},
})
for index, elem := range elems {
if index > 0 {
toks = append(toks, &Token{
Type: hclsyntax.TokenComma,
Bytes: []byte{','},
})
}
toks = append(toks, elem...)
}
toks = append(toks, &Token{
Type: hclsyntax.TokenCBrack,
Bytes: []byte{']'},
})
format(toks) // fiddle with the SpacesBefore field to get canonical spacing
return toks
}
// TokensForObject returns a sequence of tokens that represents an object
// constructor, with attribute name/value pairs populated from the given
// list of attribute token objects.
//
// TokensForObject includes the given tokens verbatim into the name and
// value positions in the resulting object expression, without any validation
// to ensure that they represent valid expressions. Use TokensForValue or
// TokensForTraversal to generate valid leaf expression values, or use
// TokensForTuple, TokensForObject, and TokensForFunctionCall to
// generate other nested compound expressions.
//
// Note that HCL requires placing a traversal expression in parentheses if
// you intend to use it as an attribute name expression, because otherwise
// the parser will interpret it as a literal attribute name. TokensForObject
// does not handle that situation automatically, so a caller must add the
// necessary `TokenOParen` and TokenCParen` manually if needed.
func TokensForObject(attrs []ObjectAttrTokens) Tokens {
var toks Tokens
toks = append(toks, &Token{
Type: hclsyntax.TokenOBrace,
Bytes: []byte{'{'},
})
if len(attrs) > 0 {
toks = append(toks, &Token{
Type: hclsyntax.TokenNewline,
Bytes: []byte{'\n'},
})
}
for _, attr := range attrs {
toks = append(toks, attr.Name...)
toks = append(toks, &Token{
Type: hclsyntax.TokenEqual,
Bytes: []byte{'='},
})
toks = append(toks, attr.Value...)
toks = append(toks, &Token{
Type: hclsyntax.TokenNewline,
Bytes: []byte{'\n'},
})
}
toks = append(toks, &Token{
Type: hclsyntax.TokenCBrace,
Bytes: []byte{'}'},
})
format(toks) // fiddle with the SpacesBefore field to get canonical spacing
return toks
}
// TokensForFunctionCall returns a sequence of tokens that represents call
// to the function with the given name, using the argument tokens to
// populate the argument expressions.
//
// TokensForFunctionCall includes the given argument tokens verbatim into the
// positions in the resulting call expression, without any validation
// to ensure that they represent valid expressions. Use TokensForValue or
// TokensForTraversal to generate valid leaf expression values, or use
// TokensForTuple, TokensForObject, and TokensForFunctionCall to
// generate other nested compound expressions.
//
// This function doesn't include an explicit way to generate the expansion
// symbol "..." on the final argument. Currently, generating that requires
// manually appending a TokenEllipsis with the bytes "..." to the tokens for
// the final argument.
func TokensForFunctionCall(funcName string, args ...Tokens) Tokens {
var toks Tokens
toks = append(toks, TokensForIdentifier(funcName)...)
toks = append(toks, &Token{
Type: hclsyntax.TokenOParen,
Bytes: []byte{'('},
})
for index, arg := range args {
if index > 0 {
toks = append(toks, &Token{
Type: hclsyntax.TokenComma,
Bytes: []byte{','},
})
}
toks = append(toks, arg...)
}
toks = append(toks, &Token{
Type: hclsyntax.TokenCParen,
Bytes: []byte{')'},
})
format(toks) // fiddle with the SpacesBefore field to get canonical spacing
return toks
}
func appendTokensForValue(val cty.Value, toks Tokens) Tokens {
switch {

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclwrite
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclwrite
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclwrite
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclwrite
import (

View File

@ -1,10 +1,13 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclwrite
import (
"bytes"
"io"
"github.com/apparentlymart/go-textseg/v12/textseg"
"github.com/apparentlymart/go-textseg/v15/textseg"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
)
@ -114,6 +117,16 @@ func (ts Tokens) BuildTokens(to Tokens) Tokens {
return append(to, ts...)
}
// ObjectAttrTokens represents the raw tokens for the name and value of
// one attribute in an object constructor expression.
//
// This is defined primarily for use with function TokensForObject. See
// that function's documentation for more information.
type ObjectAttrTokens struct {
Name Tokens
Value Tokens
}
func newIdentToken(name string) *Token {
return &Token{
Type: hclsyntax.TokenIdent,

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package json
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package json
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Package json is the JSON parser for HCL. It parses JSON files and returns
// implementations of the core HCL structural interfaces in terms of the
// JSON data inside.

57
vendor/github.com/hashicorp/hcl/v2/json/is.go generated vendored Normal file
View File

@ -0,0 +1,57 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package json
import (
"github.com/hashicorp/hcl/v2"
)
// IsJSONExpression returns true if and only if the given expression is one
// that originated in a JSON document.
//
// Applications aiming to be syntax-agnostic should not use this function and
// should instead use the normal expression evaluation or static analysis
// APIs.
//
// However, JSON expressions do have a unique behavior whereby they interpret
// the source JSON differently depending on the hcl.EvalContext value passed
// to the Value method -- in particular, a nil hcl.EvalContext returns
// literal strings rather than interpreting them as HCL template syntax --
// and so in exceptional cases an application may wish to rely on that behavior
// in situations where it specifically knows the expression originated in JSON,
// in case it needs to do some non-standard handling of the expression in that
// case.
//
// Caution: The normal HCL API allows for HCL expression implementations that
// wrap other HCL expression implementations. This function will return false
// if given an expression of some other type that encapsulates a JSON
// expression, even if the wrapper implementation would in principle preserve
// the special evaluation behavior of the wrapped expression.
func IsJSONExpression(maybeJSONExpr hcl.Expression) bool {
_, ok := maybeJSONExpr.(*expression)
return ok
}
// IsJSONBody returns true if and only if the given body is one that originated
// in a JSON document.
//
// Applications aiming to be syntax-agnostic should not use this function and
// should instead use the normal schema-driven or "just attributes' decoding
// APIs.
//
// Howeer, JSON expressions do have a unique behavior whereby various different
// source JSON shapes can be interpreted in different ways depending on the
// given schema, and so in exceptional cases an application may need to
// perform some deeper analysis first in order to distinguish variants of
// different physical structure.
//
// Caution: The normal HCL API allows for HCL body implementations that wrap
// other HCL body implementations. This function will return false if given an
// expression of some other type that encapsulates a JSON body, even if
// the wrapper implementation would in principle preserve the special
// decoding behavior of the wrapped body.
func IsJSONBody(maybeJSONBody hcl.Body) bool {
_, ok := maybeJSONBody.(*body)
return ok
}

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package json
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package json
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package json
type peeker struct {

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package json
import (

View File

@ -1,9 +1,12 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package json
import (
"fmt"
"github.com/apparentlymart/go-textseg/v12/textseg"
"github.com/apparentlymart/go-textseg/v15/textseg"
"github.com/hashicorp/hcl/v2"
)

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package json
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (
@ -21,6 +24,8 @@ import (
// though nil can be provided if the calling application is going to
// ignore the subject of the returned diagnostics anyway.
func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics) {
const invalidIndex = "Invalid index"
if collection.IsNull() {
return cty.DynamicVal, Diagnostics{
{
@ -35,7 +40,7 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Summary: invalidIndex,
Detail: "Can't use a null value as an indexing key.",
Subject: srcRange,
},
@ -66,7 +71,7 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Summary: invalidIndex,
Detail: fmt.Sprintf(
"The given key does not identify an element in this collection value: %s.",
keyErr.Error(),
@ -88,31 +93,84 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
}
}
if has.False() {
// We have a more specialized error message for the situation of
// using a fractional number to index into a sequence, because
// that will tend to happen if the user is trying to use division
// to calculate an index and not realizing that HCL does float
// division rather than integer division.
if (ty.IsListType() || ty.IsTupleType()) && key.Type().Equals(cty.Number) {
if key.IsKnown() && !key.IsNull() {
// NOTE: we don't know what any marks might've represented
// up at the calling application layer, so we must avoid
// showing the literal number value in these error messages
// in case the mark represents something important, such as
// a value being "sensitive".
key, _ := key.Unmark()
bf := key.AsBigFloat()
if _, acc := bf.Int(nil); acc != big.Exact {
// We have a more specialized error message for the
// situation of using a fractional number to index into
// a sequence, because that will tend to happen if the
// user is trying to use division to calculate an index
// and not realizing that HCL does float division
// rather than integer division.
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Detail: fmt.Sprintf("The given key does not identify an element in this collection value: indexing a sequence requires a whole number, but the given index (%g) has a fractional part.", bf),
Summary: invalidIndex,
Detail: "The given key does not identify an element in this collection value: indexing a sequence requires a whole number, but the given index has a fractional part.",
Subject: srcRange,
},
}
}
if bf.Sign() < 0 {
// Some other languages allow negative indices to
// select "backwards" from the end of the sequence,
// but HCL doesn't do that in order to give better
// feedback if a dynamic index is calculated
// incorrectly.
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: invalidIndex,
Detail: "The given key does not identify an element in this collection value: a negative number is not a valid index for a sequence.",
Subject: srcRange,
},
}
}
if lenVal := collection.Length(); lenVal.IsKnown() && !lenVal.IsMarked() {
// Length always returns a number, and we already
// checked that it's a known number, so this is safe.
lenBF := lenVal.AsBigFloat()
var result big.Float
result.Sub(bf, lenBF)
if result.Sign() < 1 {
if lenBF.Sign() == 0 {
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: invalidIndex,
Detail: "The given key does not identify an element in this collection value: the collection has no elements.",
Subject: srcRange,
},
}
} else {
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: invalidIndex,
Detail: "The given key does not identify an element in this collection value: the given index is greater than or equal to the length of the collection.",
Subject: srcRange,
},
}
}
}
}
}
}
// If this is not one of the special situations we handled above
// then we'll fall back on a very generic message.
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Summary: invalidIndex,
Detail: "The given key does not identify an element in this collection value.",
Subject: srcRange,
},
@ -122,12 +180,13 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
return collection.Index(key), nil
case ty.IsObjectType():
wasNumber := key.Type() == cty.Number
key, keyErr := convert.Convert(key, cty.String)
if keyErr != nil {
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Summary: invalidIndex,
Detail: fmt.Sprintf(
"The given key does not identify an element in this collection value: %s.",
keyErr.Error(),
@ -143,14 +202,24 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
return cty.DynamicVal, nil
}
key, _ = key.Unmark()
attrName := key.AsString()
if !ty.HasAttribute(attrName) {
var suggestion string
if wasNumber {
// We note this only as an addendum to an error we would've
// already returned anyway, because it is valid (albeit weird)
// to have an attribute whose name is just decimal digits
// and then access that attribute using a number whose
// decimal representation is the same digits.
suggestion = " An object only supports looking up attributes by name, not by numeric index."
}
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Detail: "The given key does not identify an element in this collection value.",
Summary: invalidIndex,
Detail: fmt.Sprintf("The given key does not identify an element in this collection value.%s", suggestion),
Subject: srcRange,
},
}
@ -158,11 +227,21 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
return collection.GetAttr(attrName), nil
case ty.IsSetType():
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: invalidIndex,
Detail: "Elements of a set are identified only by their value and don't have any separate index or key to select with, so it's only possible to perform operations across all elements of the set.",
Subject: srcRange,
},
}
default:
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Summary: invalidIndex,
Detail: "This value does not have any indices.",
Subject: srcRange,
},
@ -195,6 +274,8 @@ func GetAttr(obj cty.Value, attrName string, srcRange *Range) (cty.Value, Diagno
}
}
const unsupportedAttr = "Unsupported attribute"
ty := obj.Type()
switch {
case ty.IsObjectType():
@ -202,7 +283,7 @@ func GetAttr(obj cty.Value, attrName string, srcRange *Range) (cty.Value, Diagno
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Unsupported attribute",
Summary: unsupportedAttr,
Detail: fmt.Sprintf("This object does not have an attribute named %q.", attrName),
Subject: srcRange,
},
@ -239,11 +320,69 @@ func GetAttr(obj cty.Value, attrName string, srcRange *Range) (cty.Value, Diagno
return obj.Index(idx), nil
case ty == cty.DynamicPseudoType:
return cty.DynamicVal, nil
case ty.IsListType() && ty.ElementType().IsObjectType():
// It seems a common mistake to try to access attributes on a whole
// list of objects rather than on a specific individual element, so
// we have some extra hints for that case.
switch {
case ty.ElementType().HasAttribute(attrName):
// This is a very strong indication that the user mistook the list
// of objects for a single object, so we can be a little more
// direct in our suggestion here.
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: unsupportedAttr,
Detail: fmt.Sprintf("Can't access attributes on a list of objects. Did you mean to access attribute %q for a specific element of the list, or across all elements of the list?", attrName),
Subject: srcRange,
},
}
default:
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: unsupportedAttr,
Detail: "Can't access attributes on a list of objects. Did you mean to access an attribute for a specific element of the list, or across all elements of the list?",
Subject: srcRange,
},
}
}
case ty.IsSetType() && ty.ElementType().IsObjectType():
// This is similar to the previous case, but we can't give such a
// direct suggestion because there is no mechanism to select a single
// item from a set.
// We could potentially suggest using a for expression or splat
// operator here, but we typically don't get into syntax specifics
// in hcl.GetAttr suggestions because it's a general function used in
// various other situations, such as in application-specific operations
// that might have a more constraint set of alternative approaches.
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: unsupportedAttr,
Detail: "Can't access attributes on a set of objects. Did you mean to access an attribute across all elements of the set?",
Subject: srcRange,
},
}
case ty.IsPrimitiveType():
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: unsupportedAttr,
Detail: fmt.Sprintf("Can't access attributes on a primitive-typed value (%s).", ty.FriendlyName()),
Subject: srcRange,
},
}
default:
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Unsupported attribute",
Summary: unsupportedAttr,
Detail: "This value does not have any attributes.",
Subject: srcRange,
},

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import "fmt"

View File

@ -1,10 +1,13 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (
"bufio"
"bytes"
"github.com/apparentlymart/go-textseg/v12/textseg"
"github.com/apparentlymart/go-textseg/v15/textseg"
)
// RangeScanner is a helper that will scan over a buffer using a bufio.SplitFunc

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
// BlockHeaderSchema represents the shape of a block header, and is

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
// -----------------------------------------------------------------------------

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
// AbsTraversalForExpr attempts to interpret the given expression as
@ -13,7 +16,7 @@ package hcl
//
// In most cases the calling application is interested in the value
// that results from an expression, but in rarer cases the application
// needs to see the the name of the variable and subsequent
// needs to see the name of the variable and subsequent
// attributes/indexes itself, for example to allow users to give references
// to the variables themselves rather than to their values. An implementer
// of this function should at least support attribute and index steps.