gohcl: WithRange[T] for concise decoding with source locations #516
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Until now
gohcl
has forced callers to decide between two non-ideal options:hcl.Expression
and then separately decode that expression into a normal type, which gives access to the source location but requires an extra decoding step downstream.Source location information is important if subsequent code does any further validation of the value beyond what HCL can do itself, and so this forces implementers to decide between returning low-quality error messages or writing more lines of code for decoding, and since a lot of people would prefer to write less code despite the consequences, this has therefore led to software with poor-quality error messages.
Go 1.18 provides a compromise: we can use a generic struct type to pair up any normal value with an
hcl.Range
value, thus still achieving a one-shot decode behavior for simpler applications but giving those applications convenient access to both the resulting value and the source location it came from.This is essentially just another "special" type that
gohcl
has bespoke handling for, similar tohcl.Body
,hcl.Expression
, andhcl.Attributes
. If decoding an expression into a value of aWithRange[T]
type then it behaves as if decoding into aT
directly, but writes the result into theValue
field of theWithRange[T]
alongside the source range.An important goal here is to do this without requiring all callers of this module to use Go 1.18. To achieve that, the generic code is all in a conditionally-compiled file, and we assume that any caller which includes
WithRange[T]
would inherently require 1.18 itself anyway, so we won't enter the codepath for handling this type on older versions of Go.This is just a prototype for now. It needs some more thorough testing with various interesting variations to see if it's behavior is reasonable in each case. For example:
WithRange[cty.Value]
*WithRange[Foo]
vs.WithRange[*Foo]
for optional attributes.WithRange[hcl.Expression]
(redundant, but should either work or return an explicit error explaining why not)WithRange[T]
for a field representing a nested block type, rather than one representing an attribute? Should the range in that case be the block'sDeclRange
, or something else?WithRange[T]
on a,remain
-tagged field? (Probably not, but it should still return a sensible error message if someone tries.)