vendor: go get github.com/hashicorp/hcl/v2@v2.3.0
This brings in the new HCL extension functions "try", "can", and "convert", along with the underlying HCL and cty infrastructure that allow them to work.
This commit is contained in:
parent
7500fa7a39
commit
b62e9a7227
4
go.mod
4
go.mod
|
@ -69,7 +69,7 @@ require (
|
|||
github.com/hashicorp/go-uuid v1.0.1
|
||||
github.com/hashicorp/go-version v1.2.0
|
||||
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f
|
||||
github.com/hashicorp/hcl/v2 v2.2.0
|
||||
github.com/hashicorp/hcl/v2 v2.3.0
|
||||
github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590
|
||||
github.com/hashicorp/memberlist v0.1.0 // indirect
|
||||
github.com/hashicorp/serf v0.0.0-20160124182025-e4ec8cc423bb // indirect
|
||||
|
@ -120,7 +120,7 @@ require (
|
|||
github.com/xanzy/ssh-agent v0.2.1
|
||||
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18 // indirect
|
||||
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557
|
||||
github.com/zclconf/go-cty v1.1.1
|
||||
github.com/zclconf/go-cty v1.2.1
|
||||
github.com/zclconf/go-cty-yaml v1.0.1
|
||||
go.uber.org/atomic v1.3.2 // indirect
|
||||
go.uber.org/multierr v1.1.0 // indirect
|
||||
|
|
10
go.sum
10
go.sum
|
@ -239,8 +239,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
|
|||
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f h1:UdxlrJz4JOnY8W+DbLISwf2B8WXEolNRA8BGCwI9jws=
|
||||
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||
github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90=
|
||||
github.com/hashicorp/hcl/v2 v2.2.0 h1:ZQ1eNLggMfTyFBhV8swxT081mlaRjr4EG85NEjjLB84=
|
||||
github.com/hashicorp/hcl/v2 v2.2.0/go.mod h1:MD4q2LOluJ5pRwTVkCXmJOY7ODWDXVXGVB8LY0t7wig=
|
||||
github.com/hashicorp/hcl/v2 v2.3.0 h1:iRly8YaMwTBAKhn1Ybk7VSdzbnopghktCD031P8ggUE=
|
||||
github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8=
|
||||
github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590 h1:2yzhWGdgQUWZUCNK+AoO35V+HTsgEmcM4J9IkArh7PI=
|
||||
github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590/go.mod h1:n2TSygSNwsLJ76m8qFXTSc7beTb+auJxYdqrnoqwZWE=
|
||||
github.com/hashicorp/memberlist v0.1.0 h1:qSsCiC0WYD39lbSitKNt40e30uorm2Ss/d4JGU1hzH8=
|
||||
|
@ -412,8 +412,10 @@ github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557/go.mod h1:ce1O1j6Ut
|
|||
github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
||||
github.com/zclconf/go-cty v1.1.0 h1:uJwc9HiBOCpoKIObTQaLR+tsEXx1HBHnOsOOpcdhZgw=
|
||||
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
||||
github.com/zclconf/go-cty v1.1.1 h1:Shl2p9Dat0cqJfXu0DZa+cOTRPhXQjK8IYWD6GVfiqo=
|
||||
github.com/zclconf/go-cty v1.1.1/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
||||
github.com/zclconf/go-cty v1.2.0 h1:sPHsy7ADcIZQP3vILvTjrh74ZA175TFP5vqiNK1UmlI=
|
||||
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||
github.com/zclconf/go-cty v1.2.1 h1:vGMsygfmeCl4Xb6OA5U5XVAaQZ69FvoG7X2jUtQujb8=
|
||||
github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||
github.com/zclconf/go-cty-yaml v1.0.1 h1:up11wlgAaDvlAGENcFDnZgkn0qUJurso7k6EpURKNF8=
|
||||
github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
# HCL Changelog
|
||||
|
||||
## v2.3.0 (Jan 3, 2020)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* ext/tryfunc: Optional functions `try` and `can` to include in your `hcl.EvalContext` when evaluating expressions, which allow users to make decisions based on the success of expressions. ([#330](https://github.com/hashicorp/hcl/pull/330))
|
||||
* ext/typeexpr: Now has an optional function `convert` which you can include in your `hcl.EvalContext` when evaluating expressions, allowing users to convert values to specific type constraints using the type constraint expression syntax. ([#330](https://github.com/hashicorp/hcl/pull/330))
|
||||
* ext/typeexpr: A new `cty` capsule type `typeexpr.TypeConstraintType` which, when used as either a type constraint for a function parameter or as a type constraint for a `hcldec` attribute specification will cause the given expression to be interpreted as a type constraint expression rather than a value expression. ([#330](https://github.com/hashicorp/hcl/pull/330))
|
||||
* ext/customdecode: An optional extension that allows overriding the static decoding behavior for expressions either in function arguments or `hcldec` attribute specifications. ([#330](https://github.com/hashicorp/hcl/pull/330))
|
||||
* ext/customdecode: New `cty` capsuletypes `customdecode.ExpressionType` and `customdecode.ExpressionClosureType` which, when used as either a type constraint for a function parameter or as a type constraint for a `hcldec` attribute specification will cause the given expression (and, for the closure type, also the `hcl.EvalContext` it was evaluated in) to be captured for later analysis, rather than immediately evaluated. ([#330](https://github.com/hashicorp/hcl/pull/330))
|
||||
|
||||
## v2.2.0 (Dec 11, 2019)
|
||||
|
||||
### Enhancements
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
# HCL Custom Static Decoding Extension
|
||||
|
||||
This HCL extension provides a mechanism for defining arguments in an HCL-based
|
||||
language whose values are derived using custom decoding rules against the
|
||||
HCL expression syntax, overriding the usual behavior of normal expression
|
||||
evaluation.
|
||||
|
||||
"Arguments", for the purpose of this extension, currently includes the
|
||||
following two contexts:
|
||||
|
||||
* For applications using `hcldec` for dynamic decoding, a `hcldec.AttrSpec`
|
||||
or `hcldec.BlockAttrsSpec` can be given a special type constraint that
|
||||
opts in to custom decoding behavior for the attribute(s) that are selected
|
||||
by that specification.
|
||||
|
||||
* When working with the HCL native expression syntax, a function given in
|
||||
the `hcl.EvalContext` during evaluation can have parameters with special
|
||||
type constraints that opt in to custom decoding behavior for the argument
|
||||
expression associated with that parameter in any call.
|
||||
|
||||
The above use-cases are rather abstract, so we'll consider a motivating
|
||||
real-world example: sometimes we (language designers) need to allow users
|
||||
to specify type constraints directly in the language itself, such as in
|
||||
[Terraform's Input Variables](https://www.terraform.io/docs/configuration/variables.html).
|
||||
Terraform's `variable` blocks include an argument called `type` which takes
|
||||
a type constraint given using HCL expression building-blocks as defined by
|
||||
[the HCL `typeexpr` extension](../typeexpr/README.md).
|
||||
|
||||
A "type constraint expression" of that sort is not an expression intended to
|
||||
be evaluated in the usual way. Instead, the physical expression is
|
||||
deconstructed using [the static analysis operations](../../spec.md#static-analysis)
|
||||
to produce a `cty.Type` as the result, rather than a `cty.Value`.
|
||||
|
||||
The purpose of this Custom Static Decoding Extension, then, is to provide a
|
||||
bridge to allow that sort of custom decoding to be used via mechanisms that
|
||||
normally deal in `cty.Value`, such as `hcldec` and native syntax function
|
||||
calls as listed above.
|
||||
|
||||
(Note: [`gohcl`](https://pkg.go.dev/github.com/hashicorp/hcl/v2/gohcl) has
|
||||
its own mechanism to support this use case, exploiting the fact that it is
|
||||
working directly with "normal" Go types. Decoding into a struct field of
|
||||
type `hcl.Expression` obtains the expression directly without evaluating it
|
||||
first. The Custom Static Decoding Extension is not necessary for that `gohcl`
|
||||
technique. You can also implement custom decoding by working directly with
|
||||
the lowest-level HCL API, which separates extraction of and evaluation of
|
||||
expressions into two steps.)
|
||||
|
||||
## Custom Decoding Types
|
||||
|
||||
This extension relies on a convention implemented in terms of
|
||||
[_Capsule Types_ in the underlying `cty` type system](https://github.com/zclconf/go-cty/blob/master/docs/types.md#capsule-types). `cty` allows a capsule type to carry arbitrary
|
||||
extension metadata values as an aid to creating higher-level abstractions like
|
||||
this extension.
|
||||
|
||||
A custom argument decoding mode, then, is implemented by creating a new `cty`
|
||||
capsule type that implements the `ExtensionData` custom operation to return
|
||||
a decoding function when requested. For example:
|
||||
|
||||
```go
|
||||
var keywordType cty.Type
|
||||
keywordType = cty.CapsuleWithOps("keyword", reflect.TypeOf(""), &cty.CapsuleOps{
|
||||
ExtensionData: func(key interface{}) interface{} {
|
||||
switch key {
|
||||
case customdecode.CustomExpressionDecoder:
|
||||
return customdecode.CustomExpressionDecoderFunc(
|
||||
func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
kw := hcl.ExprAsKeyword(expr)
|
||||
if kw == "" {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid keyword",
|
||||
Detail: "A keyword is required",
|
||||
Subject: expr.Range().Ptr(),
|
||||
})
|
||||
return cty.UnkownVal(keywordType), diags
|
||||
}
|
||||
return cty.CapsuleVal(keywordType, &kw)
|
||||
},
|
||||
)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
The boilerplate here is a bit fussy, but the important part for our purposes
|
||||
is the `case customdecode.CustomExpressionDecoder:` clause, which uses
|
||||
a custom extension key type defined in this package to recognize when a
|
||||
component implementing this extension is checking to see if a target type
|
||||
has a custom decode implementation.
|
||||
|
||||
In the above case we've defined a type that decodes expressions as static
|
||||
keywords, so a keyword like `foo` would decode as an encapsulated `"foo"`
|
||||
string, while any other sort of expression like `"baz"` or `1 + 1` would
|
||||
return an error.
|
||||
|
||||
We could then use `keywordType` as a type constraint either for a function
|
||||
parameter or a `hcldec` attribute specification, which would require the
|
||||
argument for that function parameter or the expression for the matching
|
||||
attributes to be a static keyword, rather than an arbitrary expression.
|
||||
For example, in a `hcldec.AttrSpec`:
|
||||
|
||||
```go
|
||||
keywordSpec := &hcldec.AttrSpec{
|
||||
Name: "keyword",
|
||||
Type: keywordType,
|
||||
}
|
||||
```
|
||||
|
||||
The above would accept input like the following and would set its result to
|
||||
a `cty.Value` of `keywordType`, after decoding:
|
||||
|
||||
```hcl
|
||||
keyword = foo
|
||||
```
|
||||
|
||||
## The Expression and Expression Closure `cty` types
|
||||
|
||||
Building on the above, this package also includes two capsule types that use
|
||||
the above mechanism to allow calling applications to capture expressions
|
||||
directly and thus defer analysis to a later step, after initial decoding.
|
||||
|
||||
The `customdecode.ExpressionType` type encapsulates an `hcl.Expression` alone,
|
||||
for situations like our type constraint expression example above where it's
|
||||
the static structure of the expression we want to inspect, and thus any
|
||||
variables and functions defined in the evaluation context are irrelevant.
|
||||
|
||||
The `customdecode.ExpressionClosureType` type encapsulates a
|
||||
`*customdecode.ExpressionClosure` value, which binds the given expression to
|
||||
the `hcl.EvalContext` it was asked to evaluate against and thus allows the
|
||||
receiver of that result to later perform normal evaluation of the expression
|
||||
with all the same variables and functions that would've been available to it
|
||||
naturally.
|
||||
|
||||
Both of these types can be used as type constraints either for `hcldec`
|
||||
attribute specifications or for function arguments. Here's an example of
|
||||
`ExpressionClosureType` to implement a function that can evaluate
|
||||
an expression with some additional variables defined locally, which we'll
|
||||
call the `with(...)` function:
|
||||
|
||||
```go
|
||||
var WithFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "variables",
|
||||
Type: cty.DynamicPseudoType,
|
||||
},
|
||||
{
|
||||
Name: "expression",
|
||||
Type: customdecode.ExpressionClosureType,
|
||||
},
|
||||
},
|
||||
Type: func(args []cty.Value) (cty.Type, error) {
|
||||
varsVal := args[0]
|
||||
exprVal := args[1]
|
||||
if !varsVal.Type().IsObjectType() {
|
||||
return cty.NilVal, function.NewArgErrorf(0, "must be an object defining local variables")
|
||||
}
|
||||
if !varsVal.IsKnown() {
|
||||
// We can't predict our result type until the variables object
|
||||
// is known.
|
||||
return cty.DynamicPseudoType, nil
|
||||
}
|
||||
vars := varsVal.AsValueMap()
|
||||
closure := customdecode.ExpressionClosureFromVal(exprVal)
|
||||
result, err := evalWithLocals(vars, closure)
|
||||
if err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
return result.Type(), nil
|
||||
},
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
varsVal := args[0]
|
||||
exprVal := args[1]
|
||||
vars := varsVal.AsValueMap()
|
||||
closure := customdecode.ExpressionClosureFromVal(exprVal)
|
||||
return evalWithLocals(vars, closure)
|
||||
},
|
||||
})
|
||||
|
||||
func evalWithLocals(locals map[string]cty.Value, closure *customdecode.ExpressionClosure) (cty.Value, error) {
|
||||
childCtx := closure.EvalContext.NewChild()
|
||||
childCtx.Variables = locals
|
||||
val, diags := closure.Expression.Value(childCtx)
|
||||
if diags.HasErrors() {
|
||||
return cty.NilVal, function.NewArgErrorf(1, "couldn't evaluate expression: %s", diags.Error())
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
```
|
||||
|
||||
If the above function were placed into an `hcl.EvalContext` as `with`, it
|
||||
could be used in a native syntax call to that function as follows:
|
||||
|
||||
```hcl
|
||||
foo = with({name = "Cory"}, "${greeting}, ${name}!")
|
||||
```
|
||||
|
||||
The above assumes a variable in the main context called `greeting`, to which
|
||||
the `with` function adds `name` before evaluating the expression given in
|
||||
its second argument. This makes that second argument context-sensitive -- it
|
||||
would behave differently if the user wrote the same thing somewhere else -- so
|
||||
this capability should be used with care to make sure it doesn't cause confusion
|
||||
for the end-users of your language.
|
||||
|
||||
There are some other examples of this capability to evaluate expressions in
|
||||
unusual ways in the `tryfunc` directory that is a sibling of this one.
|
56
vendor/github.com/hashicorp/hcl/v2/ext/customdecode/customdecode.go
generated
vendored
Normal file
56
vendor/github.com/hashicorp/hcl/v2/ext/customdecode/customdecode.go
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
// Package customdecode contains a HCL extension that allows, in certain
|
||||
// contexts, expression evaluation to be overridden by custom static analysis.
|
||||
//
|
||||
// This mechanism is only supported in certain specific contexts where
|
||||
// expressions are decoded with a specific target type in mind. For more
|
||||
// information, see the documentation on CustomExpressionDecoder.
|
||||
package customdecode
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
type customDecoderImpl int
|
||||
|
||||
// CustomExpressionDecoder is a value intended to be used as a cty capsule
|
||||
// type ExtensionData key for capsule types whose values are to be obtained
|
||||
// by static analysis of an expression rather than normal evaluation of that
|
||||
// expression.
|
||||
//
|
||||
// When a cooperating capsule type is asked for ExtensionData with this key,
|
||||
// it must return a non-nil CustomExpressionDecoderFunc value.
|
||||
//
|
||||
// This mechanism is not universally supported; instead, it's handled in a few
|
||||
// specific places where expressions are evaluated with the intent of producing
|
||||
// a cty.Value of a type given by the calling application.
|
||||
//
|
||||
// Specifically, this currently works for type constraints given in
|
||||
// hcldec.AttrSpec and hcldec.BlockAttrsSpec, and it works for arguments to
|
||||
// function calls in the HCL native syntax. HCL extensions implemented outside
|
||||
// of the main HCL module may also implement this; consult their own
|
||||
// documentation for details.
|
||||
const CustomExpressionDecoder = customDecoderImpl(1)
|
||||
|
||||
// CustomExpressionDecoderFunc is the type of value that must be returned by
|
||||
// a capsule type handling the key CustomExpressionDecoder in its ExtensionData
|
||||
// implementation.
|
||||
//
|
||||
// If no error diagnostics are returned, the result value MUST be of the
|
||||
// capsule type that the decoder function was derived from. If the returned
|
||||
// error diagnostics prevent producing a value at all, return cty.NilVal.
|
||||
type CustomExpressionDecoderFunc func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
|
||||
|
||||
// CustomExpressionDecoderForType takes any cty type and returns its
|
||||
// custom expression decoder implementation if it has one. If it is not a
|
||||
// capsule type or it does not implement a custom expression decoder, this
|
||||
// function returns nil.
|
||||
func CustomExpressionDecoderForType(ty cty.Type) CustomExpressionDecoderFunc {
|
||||
if !ty.IsCapsuleType() {
|
||||
return nil
|
||||
}
|
||||
if fn, ok := ty.CapsuleExtensionData(CustomExpressionDecoder).(CustomExpressionDecoderFunc); ok {
|
||||
return fn
|
||||
}
|
||||
return nil
|
||||
}
|
146
vendor/github.com/hashicorp/hcl/v2/ext/customdecode/expression_type.go
generated
vendored
Normal file
146
vendor/github.com/hashicorp/hcl/v2/ext/customdecode/expression_type.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
|||
package customdecode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// ExpressionType is a cty capsule type that carries hcl.Expression values.
|
||||
//
|
||||
// This type implements custom decoding in the most general way possible: it
|
||||
// just captures whatever expression is given to it, with no further processing
|
||||
// whatsoever. It could therefore be useful in situations where an application
|
||||
// must defer processing of the expression content until a later step.
|
||||
//
|
||||
// ExpressionType only captures the expression, not the evaluation context it
|
||||
// was destined to be evaluated in. That means this type can be fine for
|
||||
// situations where the recipient of the value only intends to do static
|
||||
// analysis, but ExpressionClosureType is more appropriate in situations where
|
||||
// the recipient will eventually evaluate the given expression.
|
||||
var ExpressionType cty.Type
|
||||
|
||||
// ExpressionVal returns a new cty value of type ExpressionType, wrapping the
|
||||
// given expression.
|
||||
func ExpressionVal(expr hcl.Expression) cty.Value {
|
||||
return cty.CapsuleVal(ExpressionType, &expr)
|
||||
}
|
||||
|
||||
// ExpressionFromVal returns the expression encapsulated in the given value, or
|
||||
// panics if the value is not a known value of ExpressionType.
|
||||
func ExpressionFromVal(v cty.Value) hcl.Expression {
|
||||
if !v.Type().Equals(ExpressionType) {
|
||||
panic("value is not of ExpressionType")
|
||||
}
|
||||
ptr := v.EncapsulatedValue().(*hcl.Expression)
|
||||
return *ptr
|
||||
}
|
||||
|
||||
// ExpressionClosureType is a cty capsule type that carries hcl.Expression
|
||||
// values along with their original evaluation contexts.
|
||||
//
|
||||
// This is similar to ExpressionType except that during custom decoding it
|
||||
// also captures the hcl.EvalContext that was provided, allowing callers to
|
||||
// evaluate the expression later in the same context where it would originally
|
||||
// have been evaluated, or a context derived from that one.
|
||||
var ExpressionClosureType cty.Type
|
||||
|
||||
// ExpressionClosure is the type encapsulated in ExpressionClosureType
|
||||
type ExpressionClosure struct {
|
||||
Expression hcl.Expression
|
||||
EvalContext *hcl.EvalContext
|
||||
}
|
||||
|
||||
// ExpressionClosureVal returns a new cty value of type ExpressionClosureType,
|
||||
// wrapping the given expression closure.
|
||||
func ExpressionClosureVal(closure *ExpressionClosure) cty.Value {
|
||||
return cty.CapsuleVal(ExpressionClosureType, closure)
|
||||
}
|
||||
|
||||
// Value evaluates the closure's expression using the closure's EvalContext,
|
||||
// returning the result.
|
||||
func (c *ExpressionClosure) Value() (cty.Value, hcl.Diagnostics) {
|
||||
return c.Expression.Value(c.EvalContext)
|
||||
}
|
||||
|
||||
// ExpressionClosureFromVal returns the expression closure encapsulated in the
|
||||
// given value, or panics if the value is not a known value of
|
||||
// ExpressionClosureType.
|
||||
//
|
||||
// The caller MUST NOT modify the returned closure or the EvalContext inside
|
||||
// it. To derive a new EvalContext, either create a child context or make
|
||||
// a copy.
|
||||
func ExpressionClosureFromVal(v cty.Value) *ExpressionClosure {
|
||||
if !v.Type().Equals(ExpressionClosureType) {
|
||||
panic("value is not of ExpressionClosureType")
|
||||
}
|
||||
return v.EncapsulatedValue().(*ExpressionClosure)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Getting hold of a reflect.Type for hcl.Expression is a bit tricky because
|
||||
// it's an interface type, but we can do it with some indirection.
|
||||
goExpressionType := reflect.TypeOf((*hcl.Expression)(nil)).Elem()
|
||||
|
||||
ExpressionType = cty.CapsuleWithOps("expression", goExpressionType, &cty.CapsuleOps{
|
||||
ExtensionData: func(key interface{}) interface{} {
|
||||
switch key {
|
||||
case CustomExpressionDecoder:
|
||||
return CustomExpressionDecoderFunc(
|
||||
func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
return ExpressionVal(expr), nil
|
||||
},
|
||||
)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
},
|
||||
TypeGoString: func(_ reflect.Type) string {
|
||||
return "customdecode.ExpressionType"
|
||||
},
|
||||
GoString: func(raw interface{}) string {
|
||||
exprPtr := raw.(*hcl.Expression)
|
||||
return fmt.Sprintf("customdecode.ExpressionVal(%#v)", *exprPtr)
|
||||
},
|
||||
RawEquals: func(a, b interface{}) bool {
|
||||
aPtr := a.(*hcl.Expression)
|
||||
bPtr := b.(*hcl.Expression)
|
||||
return reflect.DeepEqual(*aPtr, *bPtr)
|
||||
},
|
||||
})
|
||||
ExpressionClosureType = cty.CapsuleWithOps("expression closure", reflect.TypeOf(ExpressionClosure{}), &cty.CapsuleOps{
|
||||
ExtensionData: func(key interface{}) interface{} {
|
||||
switch key {
|
||||
case CustomExpressionDecoder:
|
||||
return CustomExpressionDecoderFunc(
|
||||
func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
return ExpressionClosureVal(&ExpressionClosure{
|
||||
Expression: expr,
|
||||
EvalContext: ctx,
|
||||
}), nil
|
||||
},
|
||||
)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
},
|
||||
TypeGoString: func(_ reflect.Type) string {
|
||||
return "customdecode.ExpressionClosureType"
|
||||
},
|
||||
GoString: func(raw interface{}) string {
|
||||
closure := raw.(*ExpressionClosure)
|
||||
return fmt.Sprintf("customdecode.ExpressionClosureVal(%#v)", closure)
|
||||
},
|
||||
RawEquals: func(a, b interface{}) bool {
|
||||
closureA := a.(*ExpressionClosure)
|
||||
closureB := b.(*ExpressionClosure)
|
||||
// The expression itself compares by deep equality, but EvalContexts
|
||||
// conventionally compare by pointer identity, so we'll comply
|
||||
// with both conventions here by testing them separately.
|
||||
return closureA.EvalContext == closureB.EvalContext &&
|
||||
reflect.DeepEqual(closureA.Expression, closureB.Expression)
|
||||
},
|
||||
})
|
||||
}
|
|
@ -65,3 +65,71 @@ type checking it will be one that has identifiers as its attributes; object
|
|||
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.
|
||||
|
||||
## Type Constraints as Values
|
||||
|
||||
Along with defining a convention for writing down types using HCL expression
|
||||
constructs, this package also includes a mechanism for representing types as
|
||||
values that can be used as data within an HCL-based language.
|
||||
|
||||
`typeexpr.TypeConstraintType` is a
|
||||
[`cty` capsule type](https://github.com/zclconf/go-cty/blob/master/docs/types.md#capsule-types)
|
||||
that encapsulates `cty.Type` values. You can construct such a value directly
|
||||
using the `TypeConstraintVal` function:
|
||||
|
||||
```go
|
||||
tyVal := typeexpr.TypeConstraintVal(cty.String)
|
||||
|
||||
// We can unpack the type from a value using TypeConstraintFromVal
|
||||
ty := typeExpr.TypeConstraintFromVal(tyVal)
|
||||
```
|
||||
|
||||
However, the primary purpose of `typeexpr.TypeConstraintType` is to be
|
||||
specified as the type constraint for an argument, in which case it serves
|
||||
as a signal for HCL to treat the argument expression as a type constraint
|
||||
expression as defined above, rather than as a normal value expression.
|
||||
|
||||
"An argument" in the above in practice means the following two locations:
|
||||
|
||||
* As the type constraint for a parameter of a cty function that will be
|
||||
used in an `hcl.EvalContext`. In that case, function calls in the HCL
|
||||
native expression syntax will require the argument to be valid type constraint
|
||||
expression syntax and the function implementation will receive a
|
||||
`TypeConstraintType` value as the argument value for that parameter.
|
||||
|
||||
* As the type constraint for a `hcldec.AttrSpec` or `hcldec.BlockAttrsSpec`
|
||||
when decoding an HCL body using `hcldec`. In that case, the attributes
|
||||
with that type constraint will be required to be valid type constraint
|
||||
expression syntax and the result will be a `TypeConstraintType` value.
|
||||
|
||||
Note that the special handling of these arguments means that an argument
|
||||
marked in this way must use the type constraint syntax directly. It is not
|
||||
valid to pass in a value of `TypeConstraintType` that has been obtained
|
||||
dynamically via some other expression result.
|
||||
|
||||
`TypeConstraintType` is provided with the intent of using it internally within
|
||||
application code when incorporating type constraint expression syntax into
|
||||
an HCL-based language, not to be used for dynamic "programming with types". A
|
||||
calling application could support programming with types by defining its _own_
|
||||
capsule type, but that is not the purpose of `TypeConstraintType`.
|
||||
|
||||
## The "convert" `cty` Function
|
||||
|
||||
Building on the `TypeConstraintType` described in the previous section, this
|
||||
package also provides `typeexpr.ConvertFunc` which is a cty function that
|
||||
can be placed into a `cty.EvalContext` (conventionally named "convert") in
|
||||
order to provide a general type conversion function in an HCL-based language:
|
||||
|
||||
```hcl
|
||||
foo = convert("true", bool)
|
||||
```
|
||||
|
||||
The second parameter uses the mechanism described in the previous section to
|
||||
require its argument to be a type constraint expression rather than a value
|
||||
expression. In doing so, it allows converting with any type constraint that
|
||||
can be expressed in this package's type constraint syntax. In the above example,
|
||||
the `foo` argument would receive a boolean true, or `cty.True` in `cty` terms.
|
||||
|
||||
The target type constraint must always be provided statically using inline
|
||||
type constraint syntax. There is no way to _dynamically_ select a type
|
||||
constraint using this function.
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
package typeexpr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/ext/customdecode"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
// TypeConstraintType is a cty capsule type that allows cty type constraints to
|
||||
// be used as values.
|
||||
//
|
||||
// If TypeConstraintType is used in a context supporting the
|
||||
// customdecode.CustomExpressionDecoder extension then it will implement
|
||||
// expression decoding using the TypeConstraint function, thus allowing
|
||||
// type expressions to be used in contexts where value expressions might
|
||||
// normally be expected, such as in arguments to function calls.
|
||||
var TypeConstraintType cty.Type
|
||||
|
||||
// TypeConstraintVal constructs a cty.Value whose type is
|
||||
// TypeConstraintType.
|
||||
func TypeConstraintVal(ty cty.Type) cty.Value {
|
||||
return cty.CapsuleVal(TypeConstraintType, &ty)
|
||||
}
|
||||
|
||||
// TypeConstraintFromVal extracts the type from a cty.Value of
|
||||
// TypeConstraintType that was previously constructed using TypeConstraintVal.
|
||||
//
|
||||
// If the given value isn't a known, non-null value of TypeConstraintType
|
||||
// then this function will panic.
|
||||
func TypeConstraintFromVal(v cty.Value) cty.Type {
|
||||
if !v.Type().Equals(TypeConstraintType) {
|
||||
panic("value is not of TypeConstraintType")
|
||||
}
|
||||
ptr := v.EncapsulatedValue().(*cty.Type)
|
||||
return *ptr
|
||||
}
|
||||
|
||||
// ConvertFunc is a cty function that implements type conversions.
|
||||
//
|
||||
// Its signature is as follows:
|
||||
// convert(value, type_constraint)
|
||||
//
|
||||
// ...where type_constraint is a type constraint expression as defined by
|
||||
// typeexpr.TypeConstraint.
|
||||
//
|
||||
// It relies on HCL's customdecode extension and so it's not suitable for use
|
||||
// in non-HCL contexts or if you are using a HCL syntax implementation that
|
||||
// does not support customdecode for function arguments. However, it _is_
|
||||
// supported for function calls in the HCL native expression syntax.
|
||||
var ConvertFunc function.Function
|
||||
|
||||
func init() {
|
||||
TypeConstraintType = cty.CapsuleWithOps("type constraint", reflect.TypeOf(cty.Type{}), &cty.CapsuleOps{
|
||||
ExtensionData: func(key interface{}) interface{} {
|
||||
switch key {
|
||||
case customdecode.CustomExpressionDecoder:
|
||||
return customdecode.CustomExpressionDecoderFunc(
|
||||
func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
ty, diags := TypeConstraint(expr)
|
||||
if diags.HasErrors() {
|
||||
return cty.NilVal, diags
|
||||
}
|
||||
return TypeConstraintVal(ty), nil
|
||||
},
|
||||
)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
},
|
||||
TypeGoString: func(_ reflect.Type) string {
|
||||
return "typeexpr.TypeConstraintType"
|
||||
},
|
||||
GoString: func(raw interface{}) string {
|
||||
tyPtr := raw.(*cty.Type)
|
||||
return fmt.Sprintf("typeexpr.TypeConstraintVal(%#v)", *tyPtr)
|
||||
},
|
||||
RawEquals: func(a, b interface{}) bool {
|
||||
aPtr := a.(*cty.Type)
|
||||
bPtr := b.(*cty.Type)
|
||||
return (*aPtr).Equals(*bPtr)
|
||||
},
|
||||
})
|
||||
|
||||
ConvertFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "value",
|
||||
Type: cty.DynamicPseudoType,
|
||||
AllowNull: true,
|
||||
AllowDynamicType: true,
|
||||
},
|
||||
{
|
||||
Name: "type",
|
||||
Type: TypeConstraintType,
|
||||
},
|
||||
},
|
||||
Type: func(args []cty.Value) (cty.Type, error) {
|
||||
wantTypePtr := args[1].EncapsulatedValue().(*cty.Type)
|
||||
got, err := convert.Convert(args[0], *wantTypePtr)
|
||||
if err != nil {
|
||||
return cty.NilType, function.NewArgError(0, err)
|
||||
}
|
||||
return got.Type(), nil
|
||||
},
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
v, err := convert.Convert(args[0], retType)
|
||||
if err != nil {
|
||||
return cty.NilVal, function.NewArgError(0, err)
|
||||
}
|
||||
return v, nil
|
||||
},
|
||||
})
|
||||
}
|
|
@ -6,7 +6,7 @@ require (
|
|||
github.com/apparentlymart/go-textseg v1.0.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/go-test/deep v1.0.3
|
||||
github.com/google/go-cmp v0.2.0
|
||||
github.com/google/go-cmp v0.3.1
|
||||
github.com/kr/pretty v0.1.0
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7
|
||||
|
@ -14,7 +14,7 @@ require (
|
|||
github.com/sergi/go-diff v1.0.0
|
||||
github.com/spf13/pflag v1.0.2
|
||||
github.com/stretchr/testify v1.2.2 // indirect
|
||||
github.com/zclconf/go-cty v1.1.1
|
||||
github.com/zclconf/go-cty v1.2.0
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734
|
||||
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82 // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
|
|
|
@ -9,8 +9,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
|
@ -29,8 +29,8 @@ github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
|||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/zclconf/go-cty v1.1.1 h1:Shl2p9Dat0cqJfXu0DZa+cOTRPhXQjK8IYWD6GVfiqo=
|
||||
github.com/zclconf/go-cty v1.1.1/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
||||
github.com/zclconf/go-cty v1.2.0 h1:sPHsy7ADcIZQP3vILvTjrh74ZA175TFP5vqiNK1UmlI=
|
||||
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"sort"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/ext/customdecode"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
|
@ -193,6 +194,14 @@ func (s *AttrSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ct
|
|||
return cty.NullVal(s.Type), nil
|
||||
}
|
||||
|
||||
if decodeFn := customdecode.CustomExpressionDecoderForType(s.Type); decodeFn != nil {
|
||||
v, diags := decodeFn(attr.Expr, ctx)
|
||||
if v == cty.NilVal {
|
||||
v = cty.UnknownVal(s.Type)
|
||||
}
|
||||
return v, diags
|
||||
}
|
||||
|
||||
val, diags := attr.Expr.Value(ctx)
|
||||
|
||||
convVal, err := convert.Convert(val, s.Type)
|
||||
|
@ -1223,6 +1232,16 @@ func (s *BlockAttrsSpec) decode(content *hcl.BodyContent, blockLabels []blockLab
|
|||
|
||||
vals := make(map[string]cty.Value, len(attrs))
|
||||
for name, attr := range attrs {
|
||||
if decodeFn := customdecode.CustomExpressionDecoderForType(s.ElementType); decodeFn != nil {
|
||||
attrVal, attrDiags := decodeFn(attr.Expr, ctx)
|
||||
diags = append(diags, attrDiags...)
|
||||
if attrVal == cty.NilVal {
|
||||
attrVal = cty.UnknownVal(s.ElementType)
|
||||
}
|
||||
vals[name] = attrVal
|
||||
continue
|
||||
}
|
||||
|
||||
attrVal, attrDiags := attr.Expr.Value(ctx)
|
||||
diags = append(diags, attrDiags...)
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/ext/customdecode"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
|
@ -350,13 +351,24 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
|
|||
param = varParam
|
||||
}
|
||||
|
||||
val, argDiags := argExpr.Value(ctx)
|
||||
var val cty.Value
|
||||
if decodeFn := customdecode.CustomExpressionDecoderForType(param.Type); decodeFn != nil {
|
||||
var argDiags hcl.Diagnostics
|
||||
val, argDiags = decodeFn(argExpr, ctx)
|
||||
diags = append(diags, argDiags...)
|
||||
if val == cty.NilVal {
|
||||
val = cty.UnknownVal(param.Type)
|
||||
}
|
||||
} else {
|
||||
var argDiags hcl.Diagnostics
|
||||
val, argDiags = argExpr.Value(ctx)
|
||||
if len(argDiags) > 0 {
|
||||
diags = append(diags, argDiags...)
|
||||
}
|
||||
|
||||
// Try to convert our value to the parameter type
|
||||
val, err := convert.Convert(val, param.Type)
|
||||
var err error
|
||||
val, err = convert.Convert(val, param.Type)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
|
@ -371,6 +383,7 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
|
|||
EvalContext: ctx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
argVals[i] = val
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ type capsuleType struct {
|
|||
typeImplSigil
|
||||
Name string
|
||||
GoType reflect.Type
|
||||
Ops *CapsuleOps
|
||||
}
|
||||
|
||||
func (t *capsuleType) Equals(other Type) bool {
|
||||
|
@ -24,10 +25,22 @@ func (t *capsuleType) FriendlyName(mode friendlyTypeNameMode) string {
|
|||
}
|
||||
|
||||
func (t *capsuleType) GoString() string {
|
||||
impl := t.Ops.TypeGoString
|
||||
if impl == nil {
|
||||
// To get a useful representation of our native type requires some
|
||||
// shenanigans.
|
||||
victimVal := reflect.Zero(t.GoType)
|
||||
if t.Ops == noCapsuleOps {
|
||||
return fmt.Sprintf("cty.Capsule(%q, reflect.TypeOf(%#v))", t.Name, victimVal.Interface())
|
||||
} else {
|
||||
// Including the operations in the output will make this _very_ long,
|
||||
// so in practice any capsule type with ops ought to provide a
|
||||
// TypeGoString function to override this with something more
|
||||
// reasonable.
|
||||
return fmt.Sprintf("cty.CapsuleWithOps(%q, reflect.TypeOf(%#v), %#v)", t.Name, victimVal.Interface(), t.Ops)
|
||||
}
|
||||
}
|
||||
return impl(t.GoType)
|
||||
}
|
||||
|
||||
// Capsule creates a new Capsule type.
|
||||
|
@ -47,8 +60,11 @@ func (t *capsuleType) GoString() string {
|
|||
// use the same native type.
|
||||
//
|
||||
// Each capsule-typed value contains a pointer to a value of the given native
|
||||
// type. A capsule-typed value supports no operations except equality, and
|
||||
// equality is implemented by pointer identity of the encapsulated pointer.
|
||||
// type. A capsule-typed value by default supports no operations except
|
||||
// equality, and equality is implemented by pointer identity of the
|
||||
// encapsulated pointer. A capsule type can optionally have its own
|
||||
// implementations of certain operations if it is created with CapsuleWithOps
|
||||
// instead of Capsule.
|
||||
//
|
||||
// The given name is used as the new type's "friendly name". This can be any
|
||||
// string in principle, but will usually be a short, all-lowercase name aimed
|
||||
|
@ -65,6 +81,29 @@ func Capsule(name string, nativeType reflect.Type) Type {
|
|||
&capsuleType{
|
||||
Name: name,
|
||||
GoType: nativeType,
|
||||
Ops: noCapsuleOps,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CapsuleWithOps is like Capsule except the caller may provide an object
|
||||
// representing some overloaded operation implementations to associate with
|
||||
// the given capsule type.
|
||||
//
|
||||
// All of the other caveats and restrictions for capsule types still apply, but
|
||||
// overloaded operations can potentially help a capsule type participate better
|
||||
// in cty operations.
|
||||
func CapsuleWithOps(name string, nativeType reflect.Type, ops *CapsuleOps) Type {
|
||||
// Copy the operations to make sure the caller can't modify them after
|
||||
// we're constructed.
|
||||
ourOps := *ops
|
||||
ourOps.assertValid()
|
||||
|
||||
return Type{
|
||||
&capsuleType{
|
||||
Name: name,
|
||||
GoType: nativeType,
|
||||
Ops: &ourOps,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
package cty
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// CapsuleOps represents a set of overloaded operations for a capsule type.
|
||||
//
|
||||
// Each field is a reference to a function that can either be nil or can be
|
||||
// set to an implementation of the corresponding operation. If an operation
|
||||
// function is nil then it isn't supported for the given capsule type.
|
||||
type CapsuleOps struct {
|
||||
// GoString provides the GoString implementation for values of the
|
||||
// corresponding type. Conventionally this should return a string
|
||||
// representation of an expression that would produce an equivalent
|
||||
// value.
|
||||
GoString func(val interface{}) string
|
||||
|
||||
// TypeGoString provides the GoString implementation for the corresponding
|
||||
// capsule type itself.
|
||||
TypeGoString func(goTy reflect.Type) string
|
||||
|
||||
// Equals provides the implementation of the Equals operation. This is
|
||||
// called only with known, non-null values of the corresponding type,
|
||||
// but if the corresponding type is a compound type then it must be
|
||||
// ready to detect and handle nested unknown or null values, usually
|
||||
// by recursively calling Value.Equals on those nested values.
|
||||
//
|
||||
// The result value must always be of type cty.Bool, or the Equals
|
||||
// operation will panic.
|
||||
//
|
||||
// If RawEquals is set without also setting Equals, the RawEquals
|
||||
// implementation will be used as a fallback implementation. That fallback
|
||||
// is appropriate only for leaf types that do not contain any nested
|
||||
// cty.Value that would need to distinguish Equals vs. RawEquals for their
|
||||
// own equality.
|
||||
//
|
||||
// If RawEquals is nil then Equals must also be nil, selecting the default
|
||||
// pointer-identity comparison instead.
|
||||
Equals func(a, b interface{}) Value
|
||||
|
||||
// RawEquals provides the implementation of the RawEquals operation.
|
||||
// This is called only with known, non-null values of the corresponding
|
||||
// type, but if the corresponding type is a compound type then it must be
|
||||
// ready to detect and handle nested unknown or null values, usually
|
||||
// by recursively calling Value.RawEquals on those nested values.
|
||||
//
|
||||
// If RawEquals is nil, values of the corresponding type are compared by
|
||||
// pointer identity of the encapsulated value.
|
||||
RawEquals func(a, b interface{}) bool
|
||||
|
||||
// ConversionFrom can provide conversions from the corresponding type to
|
||||
// some other type when values of the corresponding type are used with
|
||||
// the "convert" package. (The main cty package does not use this operation.)
|
||||
//
|
||||
// This function itself returns a function, allowing it to switch its
|
||||
// behavior depending on the given source type. Return nil to indicate
|
||||
// that no such conversion is available.
|
||||
ConversionFrom func(src Type) func(interface{}, Path) (Value, error)
|
||||
|
||||
// ConversionTo can provide conversions to the corresponding type from
|
||||
// some other type when values of the corresponding type are used with
|
||||
// the "convert" package. (The main cty package does not use this operation.)
|
||||
//
|
||||
// This function itself returns a function, allowing it to switch its
|
||||
// behavior depending on the given destination type. Return nil to indicate
|
||||
// that no such conversion is available.
|
||||
ConversionTo func(dst Type) func(Value, Path) (interface{}, error)
|
||||
|
||||
// ExtensionData is an extension point for applications that wish to
|
||||
// create their own extension features using capsule types.
|
||||
//
|
||||
// The key argument is any value that can be compared with Go's ==
|
||||
// operator, but should be of a named type in a package belonging to the
|
||||
// application defining the key. An ExtensionData implementation must
|
||||
// check to see if the given key is familar to it, and if so return a
|
||||
// suitable value for the key.
|
||||
//
|
||||
// If the given key is unrecognized, the ExtensionData function must
|
||||
// return a nil interface. (Importantly, not an interface containing a nil
|
||||
// pointer of some other type.)
|
||||
// The common implementation of ExtensionData is a single switch statement
|
||||
// over "key" which has a default case returning nil.
|
||||
//
|
||||
// The meaning of any given key is entirely up to the application that
|
||||
// defines it. Applications consuming ExtensionData from capsule types
|
||||
// should do so defensively: if the result of ExtensionData is not valid,
|
||||
// prefer to ignore it or gracefully produce an error rather than causing
|
||||
// a panic.
|
||||
ExtensionData func(key interface{}) interface{}
|
||||
}
|
||||
|
||||
// noCapsuleOps is a pointer to a CapsuleOps with no functions set, which
|
||||
// is used as the default operations value when a type is created using
|
||||
// the Capsule function.
|
||||
var noCapsuleOps = &CapsuleOps{}
|
||||
|
||||
func (ops *CapsuleOps) assertValid() {
|
||||
if ops.RawEquals == nil && ops.Equals != nil {
|
||||
panic("Equals cannot be set without RawEquals")
|
||||
}
|
||||
}
|
||||
|
||||
// CapsuleOps returns a pointer to the CapsuleOps value for a capsule type,
|
||||
// or panics if the receiver is not a capsule type.
|
||||
//
|
||||
// The caller must not modify the CapsuleOps.
|
||||
func (ty Type) CapsuleOps() *CapsuleOps {
|
||||
if !ty.IsCapsuleType() {
|
||||
panic("not a capsule-typed value")
|
||||
}
|
||||
|
||||
return ty.typeImpl.(*capsuleType).Ops
|
||||
}
|
||||
|
||||
// CapsuleExtensionData is a convenience interface to the ExtensionData
|
||||
// function that can be optionally implemented for a capsule type. It will
|
||||
// check to see if the underlying type implements ExtensionData and call it
|
||||
// if so. If not, it will return nil to indicate that the given key is not
|
||||
// supported.
|
||||
//
|
||||
// See the documentation for CapsuleOps.ExtensionData for more information
|
||||
// on the purpose of and usage of this mechanism.
|
||||
//
|
||||
// If CapsuleExtensionData is called on a non-capsule type then it will panic.
|
||||
func (ty Type) CapsuleExtensionData(key interface{}) interface{} {
|
||||
ops := ty.CapsuleOps()
|
||||
if ops.ExtensionData == nil {
|
||||
return nil
|
||||
}
|
||||
return ops.ExtensionData(key)
|
||||
}
|
|
@ -16,7 +16,19 @@ func getConversion(in cty.Type, out cty.Type, unsafe bool) conversion {
|
|||
|
||||
// Wrap the conversion in some standard checks that we don't want to
|
||||
// have to repeat in every conversion function.
|
||||
return func(in cty.Value, path cty.Path) (cty.Value, error) {
|
||||
var ret conversion
|
||||
ret = func(in cty.Value, path cty.Path) (cty.Value, error) {
|
||||
if in.IsMarked() {
|
||||
// We must unmark during the conversion and then re-apply the
|
||||
// same marks to the result.
|
||||
in, inMarks := in.Unmark()
|
||||
v, err := ret(in, path)
|
||||
if v != cty.NilVal {
|
||||
v = v.WithMarks(inMarks)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
if out == cty.DynamicPseudoType {
|
||||
// Conversion to DynamicPseudoType always just passes through verbatim.
|
||||
return in, nil
|
||||
|
@ -33,6 +45,8 @@ func getConversion(in cty.Type, out cty.Type, unsafe bool) conversion {
|
|||
|
||||
return conv(in, path)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func getConversionKnown(in cty.Type, out cty.Type, unsafe bool) conversion {
|
||||
|
@ -124,6 +138,30 @@ func getConversionKnown(in cty.Type, out cty.Type, unsafe bool) conversion {
|
|||
outEty := out.ElementType()
|
||||
return conversionObjectToMap(in, outEty, unsafe)
|
||||
|
||||
case in.IsCapsuleType() || out.IsCapsuleType():
|
||||
if !unsafe {
|
||||
// Capsule types can only participate in "unsafe" conversions,
|
||||
// because we don't know enough about their conversion behaviors
|
||||
// to be sure that they will always be safe.
|
||||
return nil
|
||||
}
|
||||
if in.Equals(out) {
|
||||
// conversion to self is never allowed
|
||||
return nil
|
||||
}
|
||||
if out.IsCapsuleType() {
|
||||
if fn := out.CapsuleOps().ConversionTo; fn != nil {
|
||||
return conversionToCapsule(in, out, fn)
|
||||
}
|
||||
}
|
||||
if in.IsCapsuleType() {
|
||||
if fn := in.CapsuleOps().ConversionFrom; fn != nil {
|
||||
return conversionFromCapsule(in, out, fn)
|
||||
}
|
||||
}
|
||||
// No conversion operation is available, then.
|
||||
return nil
|
||||
|
||||
default:
|
||||
return nil
|
||||
|
||||
|
|
31
vendor/github.com/zclconf/go-cty/cty/convert/conversion_capsule.go
generated
vendored
Normal file
31
vendor/github.com/zclconf/go-cty/cty/convert/conversion_capsule.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
package convert
|
||||
|
||||
import (
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func conversionToCapsule(inTy, outTy cty.Type, fn func(inTy cty.Type) func(cty.Value, cty.Path) (interface{}, error)) conversion {
|
||||
rawConv := fn(inTy)
|
||||
if rawConv == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return func(in cty.Value, path cty.Path) (cty.Value, error) {
|
||||
rawV, err := rawConv(in, path)
|
||||
if err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
return cty.CapsuleVal(outTy, rawV), nil
|
||||
}
|
||||
}
|
||||
|
||||
func conversionFromCapsule(inTy, outTy cty.Type, fn func(outTy cty.Type) func(interface{}, cty.Path) (cty.Value, error)) conversion {
|
||||
rawConv := fn(outTy)
|
||||
if rawConv == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return func(in cty.Value, path cty.Path) (cty.Value, error) {
|
||||
return rawConv(in.EncapsulatedValue(), path)
|
||||
}
|
||||
}
|
|
@ -23,6 +23,8 @@ type ElementIterator interface {
|
|||
|
||||
func canElementIterator(val Value) bool {
|
||||
switch {
|
||||
case val.IsMarked():
|
||||
return false
|
||||
case val.ty.IsListType():
|
||||
return true
|
||||
case val.ty.IsMapType():
|
||||
|
@ -39,6 +41,7 @@ func canElementIterator(val Value) bool {
|
|||
}
|
||||
|
||||
func elementIterator(val Value) ElementIterator {
|
||||
val.assertUnmarked()
|
||||
switch {
|
||||
case val.ty.IsListType():
|
||||
return &listElementIterator{
|
||||
|
|
|
@ -47,4 +47,24 @@ type Parameter struct {
|
|||
// values are not, thus improving the type-check accuracy of derived
|
||||
// values.
|
||||
AllowDynamicType bool
|
||||
|
||||
// If AllowMarked is set then marked values may be passed into this
|
||||
// argument's slot in the implementation function. If not set, any
|
||||
// marked value will be unmarked before calling and then the markings
|
||||
// from that value will be applied automatically to the function result,
|
||||
// ensuring that the marks get propagated in a simplistic way even if
|
||||
// a function is unable to handle them.
|
||||
//
|
||||
// For any argument whose parameter has AllowMarked set, it's the
|
||||
// function implementation's responsibility to Unmark the given value
|
||||
// and propagate the marks appropriatedly to the result in order to
|
||||
// avoid losing the marks. Application-specific functions might use
|
||||
// special rules to selectively propagate particular marks.
|
||||
//
|
||||
// The automatic unmarking of values applies only to the main
|
||||
// implementation function. In an application that uses marked values,
|
||||
// the Type implementation for a function must always be prepared to accept
|
||||
// marked values, which is easy to achieve by consulting only the type
|
||||
// and ignoring the value itself.
|
||||
AllowMarked bool
|
||||
}
|
||||
|
|
|
@ -142,6 +142,21 @@ func (f Function) ReturnTypeForValues(args []cty.Value) (ty cty.Type, err error)
|
|||
for i, spec := range f.spec.Params {
|
||||
val := posArgs[i]
|
||||
|
||||
if val.IsMarked() && !spec.AllowMarked {
|
||||
// During type checking we just unmark values and discard their
|
||||
// marks, under the assumption that during actual execution of
|
||||
// the function we'll do similarly and then re-apply the marks
|
||||
// afterwards. Note that this does mean that a function that
|
||||
// inspects values (rather than just types) in its Type
|
||||
// implementation can potentially fail to take into account marks,
|
||||
// unless it specifically opts in to seeing them.
|
||||
unmarked, _ := val.Unmark()
|
||||
newArgs := make([]cty.Value, len(args))
|
||||
copy(newArgs, args)
|
||||
newArgs[i] = unmarked
|
||||
args = newArgs
|
||||
}
|
||||
|
||||
if val.IsNull() && !spec.AllowNull {
|
||||
return cty.Type{}, NewArgErrorf(i, "argument must not be null")
|
||||
}
|
||||
|
@ -168,6 +183,15 @@ func (f Function) ReturnTypeForValues(args []cty.Value) (ty cty.Type, err error)
|
|||
for i, val := range varArgs {
|
||||
realI := i + len(posArgs)
|
||||
|
||||
if val.IsMarked() && !spec.AllowMarked {
|
||||
// See the similar block in the loop above for what's going on here.
|
||||
unmarked, _ := val.Unmark()
|
||||
newArgs := make([]cty.Value, len(args))
|
||||
copy(newArgs, args)
|
||||
newArgs[realI] = unmarked
|
||||
args = newArgs
|
||||
}
|
||||
|
||||
if val.IsNull() && !spec.AllowNull {
|
||||
return cty.Type{}, NewArgErrorf(realI, "argument must not be null")
|
||||
}
|
||||
|
@ -208,9 +232,10 @@ func (f Function) Call(args []cty.Value) (val cty.Value, err error) {
|
|||
|
||||
// Type checking already dealt with most situations relating to our
|
||||
// parameter specification, but we still need to deal with unknown
|
||||
// values.
|
||||
// values and marked values.
|
||||
posArgs := args[:len(f.spec.Params)]
|
||||
varArgs := args[len(f.spec.Params):]
|
||||
var resultMarks []cty.ValueMarks
|
||||
|
||||
for i, spec := range f.spec.Params {
|
||||
val := posArgs[i]
|
||||
|
@ -218,14 +243,37 @@ func (f Function) Call(args []cty.Value) (val cty.Value, err error) {
|
|||
if !val.IsKnown() && !spec.AllowUnknown {
|
||||
return cty.UnknownVal(expectedType), nil
|
||||
}
|
||||
|
||||
if val.IsMarked() && !spec.AllowMarked {
|
||||
unwrappedVal, marks := val.Unmark()
|
||||
// In order to avoid additional overhead on applications that
|
||||
// are not using marked values, we copy the given args only
|
||||
// if we encounter a marked value we need to unmark. However,
|
||||
// as a consequence we end up doing redundant copying if multiple
|
||||
// marked values need to be unwrapped. That seems okay because
|
||||
// argument lists are generally small.
|
||||
newArgs := make([]cty.Value, len(args))
|
||||
copy(newArgs, args)
|
||||
newArgs[i] = unwrappedVal
|
||||
resultMarks = append(resultMarks, marks)
|
||||
args = newArgs
|
||||
}
|
||||
}
|
||||
|
||||
if f.spec.VarParam != nil {
|
||||
spec := f.spec.VarParam
|
||||
for _, val := range varArgs {
|
||||
for i, val := range varArgs {
|
||||
if !val.IsKnown() && !spec.AllowUnknown {
|
||||
return cty.UnknownVal(expectedType), nil
|
||||
}
|
||||
if val.IsMarked() && !spec.AllowMarked {
|
||||
unwrappedVal, marks := val.Unmark()
|
||||
newArgs := make([]cty.Value, len(args))
|
||||
copy(newArgs, args)
|
||||
newArgs[len(posArgs)+i] = unwrappedVal
|
||||
resultMarks = append(resultMarks, marks)
|
||||
args = newArgs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,6 +292,9 @@ func (f Function) Call(args []cty.Value) (val cty.Value, err error) {
|
|||
if err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
if len(resultMarks) > 0 {
|
||||
retVal = retVal.WithMarks(resultMarks...)
|
||||
}
|
||||
}
|
||||
|
||||
// Returned value must conform to what the Type function expected, to
|
||||
|
|
|
@ -11,6 +11,7 @@ var NotFunc = function.New(&function.Spec{
|
|||
Name: "val",
|
||||
Type: cty.Bool,
|
||||
AllowDynamicType: true,
|
||||
AllowMarked: true,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.Bool),
|
||||
|
@ -25,11 +26,13 @@ var AndFunc = function.New(&function.Spec{
|
|||
Name: "a",
|
||||
Type: cty.Bool,
|
||||
AllowDynamicType: true,
|
||||
AllowMarked: true,
|
||||
},
|
||||
{
|
||||
Name: "b",
|
||||
Type: cty.Bool,
|
||||
AllowDynamicType: true,
|
||||
AllowMarked: true,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.Bool),
|
||||
|
@ -44,11 +47,13 @@ var OrFunc = function.New(&function.Spec{
|
|||
Name: "a",
|
||||
Type: cty.Bool,
|
||||
AllowDynamicType: true,
|
||||
AllowMarked: true,
|
||||
},
|
||||
{
|
||||
Name: "b",
|
||||
Type: cty.Bool,
|
||||
AllowDynamicType: true,
|
||||
AllowMarked: true,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.Bool),
|
||||
|
|
|
@ -14,6 +14,7 @@ var AbsoluteFunc = function.New(&function.Spec{
|
|||
Name: "num",
|
||||
Type: cty.Number,
|
||||
AllowDynamicType: true,
|
||||
AllowMarked: true,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.Number),
|
||||
|
@ -196,11 +197,13 @@ var GreaterThanFunc = function.New(&function.Spec{
|
|||
Name: "a",
|
||||
Type: cty.Number,
|
||||
AllowDynamicType: true,
|
||||
AllowMarked: true,
|
||||
},
|
||||
{
|
||||
Name: "b",
|
||||
Type: cty.Number,
|
||||
AllowDynamicType: true,
|
||||
AllowMarked: true,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.Bool),
|
||||
|
@ -215,11 +218,13 @@ var GreaterThanOrEqualToFunc = function.New(&function.Spec{
|
|||
Name: "a",
|
||||
Type: cty.Number,
|
||||
AllowDynamicType: true,
|
||||
AllowMarked: true,
|
||||
},
|
||||
{
|
||||
Name: "b",
|
||||
Type: cty.Number,
|
||||
AllowDynamicType: true,
|
||||
AllowMarked: true,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.Bool),
|
||||
|
@ -234,11 +239,13 @@ var LessThanFunc = function.New(&function.Spec{
|
|||
Name: "a",
|
||||
Type: cty.Number,
|
||||
AllowDynamicType: true,
|
||||
AllowMarked: true,
|
||||
},
|
||||
{
|
||||
Name: "b",
|
||||
Type: cty.Number,
|
||||
AllowDynamicType: true,
|
||||
AllowMarked: true,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.Bool),
|
||||
|
@ -253,11 +260,13 @@ var LessThanOrEqualToFunc = function.New(&function.Spec{
|
|||
Name: "a",
|
||||
Type: cty.Number,
|
||||
AllowDynamicType: true,
|
||||
AllowMarked: true,
|
||||
},
|
||||
{
|
||||
Name: "b",
|
||||
Type: cty.Number,
|
||||
AllowDynamicType: true,
|
||||
AllowMarked: true,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.Bool),
|
||||
|
@ -272,6 +281,7 @@ var NegateFunc = function.New(&function.Spec{
|
|||
Name: "num",
|
||||
Type: cty.Number,
|
||||
AllowDynamicType: true,
|
||||
AllowMarked: true,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.Number),
|
||||
|
|
|
@ -3,10 +3,10 @@ package stdlib
|
|||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/apparentlymart/go-textseg/textseg"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
"github.com/apparentlymart/go-textseg/textseg"
|
||||
)
|
||||
|
||||
var UpperFunc = function.New(&function.Spec{
|
||||
|
|
|
@ -3,6 +3,7 @@ package cty
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
|
@ -15,6 +16,10 @@ import (
|
|||
// Currently it is not possible to represent values of capsule types in gob,
|
||||
// because the types themselves cannot be represented.
|
||||
func (val Value) GobEncode() ([]byte, error) {
|
||||
if val.IsMarked() {
|
||||
return nil, errors.New("value is marked")
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
enc := gob.NewEncoder(buf)
|
||||
|
||||
|
|
|
@ -9,6 +9,10 @@ import (
|
|||
)
|
||||
|
||||
func marshal(val cty.Value, t cty.Type, path cty.Path, b *bytes.Buffer) error {
|
||||
if val.IsMarked() {
|
||||
return path.NewErrorf("value has marks, so it cannot be seralized")
|
||||
}
|
||||
|
||||
// If we're going to decode as DynamicPseudoType then we need to save
|
||||
// dynamic type information to recover the real type.
|
||||
if t == cty.DynamicPseudoType && val.Type() != cty.DynamicPseudoType {
|
||||
|
|
|
@ -0,0 +1,296 @@
|
|||
package cty
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// marker is an internal wrapper type used to add special "marks" to values.
|
||||
//
|
||||
// A "mark" is an annotation that can be used to represent additional
|
||||
// characteristics of values that propagate through operation methods to
|
||||
// result values. However, a marked value cannot be used with integration
|
||||
// methods normally associated with its type, in order to ensure that
|
||||
// calling applications don't inadvertently drop marks as they round-trip
|
||||
// values out of cty and back in again.
|
||||
//
|
||||
// Marked values are created only explicitly by the calling application, so
|
||||
// an application that never marks a value does not need to worry about
|
||||
// encountering marked values.
|
||||
type marker struct {
|
||||
realV interface{}
|
||||
marks ValueMarks
|
||||
}
|
||||
|
||||
// ValueMarks is a map, representing a set, of "mark" values associated with
|
||||
// a Value. See Value.Mark for more information on the usage of mark values.
|
||||
type ValueMarks map[interface{}]struct{}
|
||||
|
||||
// NewValueMarks constructs a new ValueMarks set with the given mark values.
|
||||
func NewValueMarks(marks ...interface{}) ValueMarks {
|
||||
if len(marks) == 0 {
|
||||
return nil
|
||||
}
|
||||
ret := make(ValueMarks, len(marks))
|
||||
for _, v := range marks {
|
||||
ret[v] = struct{}{}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Equal returns true if the receiver and the given ValueMarks both contain
|
||||
// the same marks.
|
||||
func (m ValueMarks) Equal(o ValueMarks) bool {
|
||||
if len(m) != len(o) {
|
||||
return false
|
||||
}
|
||||
for v := range m {
|
||||
if _, ok := o[v]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (m ValueMarks) GoString() string {
|
||||
var s strings.Builder
|
||||
s.WriteString("cty.NewValueMarks(")
|
||||
i := 0
|
||||
for mv := range m {
|
||||
if i != 0 {
|
||||
s.WriteString(", ")
|
||||
}
|
||||
s.WriteString(fmt.Sprintf("%#v", mv))
|
||||
i++
|
||||
}
|
||||
s.WriteString(")")
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// IsMarked returns true if and only if the receiving value carries at least
|
||||
// one mark. A marked value cannot be used directly with integration methods
|
||||
// without explicitly unmarking it (and retrieving the markings) first.
|
||||
func (val Value) IsMarked() bool {
|
||||
_, ok := val.v.(marker)
|
||||
return ok
|
||||
}
|
||||
|
||||
// HasMark returns true if and only if the receiving value has the given mark.
|
||||
func (val Value) HasMark(mark interface{}) bool {
|
||||
if mr, ok := val.v.(marker); ok {
|
||||
_, ok := mr.marks[mark]
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ContainsMarked returns true if the receiving value or any value within it
|
||||
// is marked.
|
||||
//
|
||||
// This operation is relatively expensive. If you only need a shallow result,
|
||||
// use IsMarked instead.
|
||||
func (val Value) ContainsMarked() bool {
|
||||
ret := false
|
||||
Walk(val, func(_ Path, v Value) (bool, error) {
|
||||
if v.IsMarked() {
|
||||
ret = true
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
return ret
|
||||
}
|
||||
|
||||
func (val Value) assertUnmarked() {
|
||||
if val.IsMarked() {
|
||||
panic("value is marked, so must be unmarked first")
|
||||
}
|
||||
}
|
||||
|
||||
// Marks returns a map (representing a set) of all of the mark values
|
||||
// associated with the receiving value, without changing the marks. Returns nil
|
||||
// if the value is not marked at all.
|
||||
func (val Value) Marks() ValueMarks {
|
||||
if mr, ok := val.v.(marker); ok {
|
||||
// copy so that the caller can't mutate our internals
|
||||
ret := make(ValueMarks, len(mr.marks))
|
||||
for k, v := range mr.marks {
|
||||
ret[k] = v
|
||||
}
|
||||
return ret
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasSameMarks returns true if an only if the receiver and the given other
|
||||
// value have identical marks.
|
||||
func (val Value) HasSameMarks(other Value) bool {
|
||||
vm, vmOK := val.v.(marker)
|
||||
om, omOK := other.v.(marker)
|
||||
if vmOK != omOK {
|
||||
return false
|
||||
}
|
||||
if vmOK {
|
||||
return vm.marks.Equal(om.marks)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Mark returns a new value that as the same type and underlying value as
|
||||
// the receiver but that also carries the given value as a "mark".
|
||||
//
|
||||
// Marks are used to carry additional application-specific characteristics
|
||||
// associated with values. A marked value can be used with operation methods,
|
||||
// in which case the marks are propagated to the operation results. A marked
|
||||
// value _cannot_ be used with integration methods, so callers of those
|
||||
// must derive an unmarked value using Unmark (and thus explicitly handle
|
||||
// the markings) before calling the integration methods.
|
||||
//
|
||||
// The mark value can be any value that would be valid to use as a map key.
|
||||
// The mark value should be of a named type in order to use the type itself
|
||||
// as a namespace for markings. That type can be unexported if desired, in
|
||||
// order to ensure that the mark can only be handled through the defining
|
||||
// package's own functions.
|
||||
//
|
||||
// An application that never calls this method does not need to worry about
|
||||
// handling marked values.
|
||||
func (val Value) Mark(mark interface{}) Value {
|
||||
var newMarker marker
|
||||
newMarker.realV = val.v
|
||||
if mr, ok := val.v.(marker); ok {
|
||||
// It's already a marker, so we'll retain existing marks.
|
||||
newMarker.marks = make(ValueMarks, len(mr.marks)+1)
|
||||
for k, v := range mr.marks {
|
||||
newMarker.marks[k] = v
|
||||
}
|
||||
} else {
|
||||
// It's not a marker yet, so we're creating the first mark.
|
||||
newMarker.marks = make(ValueMarks, 1)
|
||||
}
|
||||
newMarker.marks[mark] = struct{}{}
|
||||
return Value{
|
||||
ty: val.ty,
|
||||
v: newMarker,
|
||||
}
|
||||
}
|
||||
|
||||
// Unmark separates the marks of the receiving value from the value itself,
|
||||
// removing a new unmarked value and a map (representing a set) of the marks.
|
||||
//
|
||||
// If the receiver isn't marked, Unmark returns it verbatim along with a nil
|
||||
// map of marks.
|
||||
func (val Value) Unmark() (Value, ValueMarks) {
|
||||
if !val.IsMarked() {
|
||||
return val, nil
|
||||
}
|
||||
mr := val.v.(marker)
|
||||
marks := val.Marks() // copy so that the caller can't mutate our internals
|
||||
return Value{
|
||||
ty: val.ty,
|
||||
v: mr.realV,
|
||||
}, marks
|
||||
}
|
||||
|
||||
// UnmarkDeep is similar to Unmark, but it works with an entire nested structure
|
||||
// rather than just the given value directly.
|
||||
//
|
||||
// The result is guaranteed to contain no nested values that are marked, and
|
||||
// the returned marks set includes the superset of all of the marks encountered
|
||||
// during the operation.
|
||||
func (val Value) UnmarkDeep() (Value, ValueMarks) {
|
||||
marks := make(ValueMarks)
|
||||
ret, _ := Transform(val, func(_ Path, v Value) (Value, error) {
|
||||
unmarkedV, valueMarks := v.Unmark()
|
||||
for m, s := range valueMarks {
|
||||
marks[m] = s
|
||||
}
|
||||
return unmarkedV, nil
|
||||
})
|
||||
return ret, marks
|
||||
}
|
||||
|
||||
func (val Value) unmarkForce() Value {
|
||||
unw, _ := val.Unmark()
|
||||
return unw
|
||||
}
|
||||
|
||||
// WithMarks returns a new value that has the same type and underlying value
|
||||
// as the receiver and also has the marks from the given maps (representing
|
||||
// sets).
|
||||
func (val Value) WithMarks(marks ...ValueMarks) Value {
|
||||
if len(marks) == 0 {
|
||||
return val
|
||||
}
|
||||
ownMarks := val.Marks()
|
||||
markCount := len(ownMarks)
|
||||
for _, s := range marks {
|
||||
markCount += len(s)
|
||||
}
|
||||
if markCount == 0 {
|
||||
return val
|
||||
}
|
||||
newMarks := make(ValueMarks, markCount)
|
||||
for m := range ownMarks {
|
||||
newMarks[m] = struct{}{}
|
||||
}
|
||||
for _, s := range marks {
|
||||
for m := range s {
|
||||
newMarks[m] = struct{}{}
|
||||
}
|
||||
}
|
||||
v := val.v
|
||||
if mr, ok := v.(marker); ok {
|
||||
v = mr.realV
|
||||
}
|
||||
return Value{
|
||||
ty: val.ty,
|
||||
v: marker{
|
||||
realV: v,
|
||||
marks: newMarks,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WithSameMarks returns a new value that has the same type and underlying
|
||||
// value as the receiver and also has the marks from the given source values.
|
||||
//
|
||||
// Use this if you are implementing your own higher-level operations against
|
||||
// cty using the integration methods, to re-introduce the marks from the
|
||||
// source values of the operation.
|
||||
func (val Value) WithSameMarks(srcs ...Value) Value {
|
||||
if len(srcs) == 0 {
|
||||
return val
|
||||
}
|
||||
ownMarks := val.Marks()
|
||||
markCount := len(ownMarks)
|
||||
for _, sv := range srcs {
|
||||
if mr, ok := sv.v.(marker); ok {
|
||||
markCount += len(mr.marks)
|
||||
}
|
||||
}
|
||||
if markCount == 0 {
|
||||
return val
|
||||
}
|
||||
newMarks := make(ValueMarks, markCount)
|
||||
for m := range ownMarks {
|
||||
newMarks[m] = struct{}{}
|
||||
}
|
||||
for _, sv := range srcs {
|
||||
if mr, ok := sv.v.(marker); ok {
|
||||
for m := range mr.marks {
|
||||
newMarks[m] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
v := val.v
|
||||
if mr, ok := v.(marker); ok {
|
||||
v = mr.realV
|
||||
}
|
||||
return Value{
|
||||
ty: val.ty,
|
||||
v: marker{
|
||||
realV: v,
|
||||
marks: newMarks,
|
||||
},
|
||||
}
|
||||
}
|
|
@ -41,6 +41,10 @@ func Marshal(val cty.Value, ty cty.Type) ([]byte, error) {
|
|||
}
|
||||
|
||||
func marshal(val cty.Value, ty cty.Type, path cty.Path, enc *msgpack.Encoder) error {
|
||||
if val.IsMarked() {
|
||||
return path.NewErrorf("value has marks, so it cannot be seralized")
|
||||
}
|
||||
|
||||
// If we're going to decode as DynamicPseudoType then we need to save
|
||||
// dynamic type information to recover the real type.
|
||||
if ty == cty.DynamicPseudoType && val.Type() != cty.DynamicPseudoType {
|
||||
|
|
|
@ -119,7 +119,13 @@ func (s ValueSet) SymmetricDifference(other ValueSet) ValueSet {
|
|||
}
|
||||
|
||||
// requireElementType panics if the given value is not of the set's element type.
|
||||
//
|
||||
// It also panics if the given value is marked, because marked values cannot
|
||||
// be stored in sets.
|
||||
func (s ValueSet) requireElementType(v Value) {
|
||||
if v.IsMarked() {
|
||||
panic("cannot store marked value directly in a set (make the set itself unknown instead)")
|
||||
}
|
||||
if !v.Type().Equals(s.ElementType()) {
|
||||
panic(fmt.Errorf("attempt to use %#v value with set of %#v", v.Type(), s.ElementType()))
|
||||
}
|
||||
|
|
|
@ -32,7 +32,10 @@ var _ set.OrderedRules = setRules{}
|
|||
// This function is not safe to use for security-related applications, since
|
||||
// the hash used is not strong enough.
|
||||
func (val Value) Hash() int {
|
||||
hashBytes := makeSetHashBytes(val)
|
||||
hashBytes, marks := makeSetHashBytes(val)
|
||||
if len(marks) > 0 {
|
||||
panic("can't take hash of value that has marks or has embedded values that have marks")
|
||||
}
|
||||
return int(crc32.ChecksumIEEE(hashBytes))
|
||||
}
|
||||
|
||||
|
@ -110,19 +113,20 @@ func (r setRules) Less(v1, v2 interface{}) bool {
|
|||
// default consistent-but-undefined ordering then. This situation is
|
||||
// not considered a compatibility constraint; callers should rely only
|
||||
// on the ordering rules for primitive values.
|
||||
v1h := makeSetHashBytes(v1v)
|
||||
v2h := makeSetHashBytes(v2v)
|
||||
v1h, _ := makeSetHashBytes(v1v)
|
||||
v2h, _ := makeSetHashBytes(v2v)
|
||||
return bytes.Compare(v1h, v2h) < 0
|
||||
}
|
||||
}
|
||||
|
||||
func makeSetHashBytes(val Value) []byte {
|
||||
func makeSetHashBytes(val Value) ([]byte, ValueMarks) {
|
||||
var buf bytes.Buffer
|
||||
appendSetHashBytes(val, &buf)
|
||||
return buf.Bytes()
|
||||
marks := make(ValueMarks)
|
||||
appendSetHashBytes(val, &buf, marks)
|
||||
return buf.Bytes(), marks
|
||||
}
|
||||
|
||||
func appendSetHashBytes(val Value, buf *bytes.Buffer) {
|
||||
func appendSetHashBytes(val Value, buf *bytes.Buffer, marks ValueMarks) {
|
||||
// Exactly what bytes we generate here don't matter as long as the following
|
||||
// constraints hold:
|
||||
// - Unknown and null values all generate distinct strings from
|
||||
|
@ -136,6 +140,19 @@ func appendSetHashBytes(val Value, buf *bytes.Buffer) {
|
|||
// the Equivalent function will still distinguish values, but set
|
||||
// performance will be best if we are able to produce a distinct string
|
||||
// for each distinct value, unknown values notwithstanding.
|
||||
|
||||
// Marks aren't considered part of a value for equality-testing purposes,
|
||||
// so we'll unmark our value before we work with it but we'll remember
|
||||
// the marks in case the caller needs to re-apply them to a derived
|
||||
// value.
|
||||
if val.IsMarked() {
|
||||
unmarkedVal, valMarks := val.Unmark()
|
||||
for m := range valMarks {
|
||||
marks[m] = struct{}{}
|
||||
}
|
||||
val = unmarkedVal
|
||||
}
|
||||
|
||||
if !val.IsKnown() {
|
||||
buf.WriteRune('?')
|
||||
return
|
||||
|
@ -175,9 +192,9 @@ func appendSetHashBytes(val Value, buf *bytes.Buffer) {
|
|||
if val.ty.IsMapType() {
|
||||
buf.WriteRune('{')
|
||||
val.ForEachElement(func(keyVal, elementVal Value) bool {
|
||||
appendSetHashBytes(keyVal, buf)
|
||||
appendSetHashBytes(keyVal, buf, marks)
|
||||
buf.WriteRune(':')
|
||||
appendSetHashBytes(elementVal, buf)
|
||||
appendSetHashBytes(elementVal, buf, marks)
|
||||
buf.WriteRune(';')
|
||||
return false
|
||||
})
|
||||
|
@ -188,7 +205,7 @@ func appendSetHashBytes(val Value, buf *bytes.Buffer) {
|
|||
if val.ty.IsListType() || val.ty.IsSetType() {
|
||||
buf.WriteRune('[')
|
||||
val.ForEachElement(func(keyVal, elementVal Value) bool {
|
||||
appendSetHashBytes(elementVal, buf)
|
||||
appendSetHashBytes(elementVal, buf, marks)
|
||||
buf.WriteRune(';')
|
||||
return false
|
||||
})
|
||||
|
@ -204,7 +221,7 @@ func appendSetHashBytes(val Value, buf *bytes.Buffer) {
|
|||
}
|
||||
sort.Strings(attrNames)
|
||||
for _, attrName := range attrNames {
|
||||
appendSetHashBytes(val.GetAttr(attrName), buf)
|
||||
appendSetHashBytes(val.GetAttr(attrName), buf, marks)
|
||||
buf.WriteRune(';')
|
||||
}
|
||||
buf.WriteRune('>')
|
||||
|
@ -214,7 +231,7 @@ func appendSetHashBytes(val Value, buf *bytes.Buffer) {
|
|||
if val.ty.IsTupleType() {
|
||||
buf.WriteRune('<')
|
||||
val.ForEachElement(func(keyVal, elementVal Value) bool {
|
||||
appendSetHashBytes(elementVal, buf)
|
||||
appendSetHashBytes(elementVal, buf, marks)
|
||||
buf.WriteRune(';')
|
||||
return false
|
||||
})
|
||||
|
|
|
@ -45,6 +45,9 @@ func (val Value) Type() Type {
|
|||
// operating on other unknown values, and so an application that never
|
||||
// introduces Unknown values can be guaranteed to never receive any either.
|
||||
func (val Value) IsKnown() bool {
|
||||
if val.IsMarked() {
|
||||
return val.unmarkForce().IsKnown()
|
||||
}
|
||||
return val.v != unknown
|
||||
}
|
||||
|
||||
|
@ -53,6 +56,9 @@ func (val Value) IsKnown() bool {
|
|||
// produces null, so an application that never introduces Null values can
|
||||
// be guaranteed to never receive any either.
|
||||
func (val Value) IsNull() bool {
|
||||
if val.IsMarked() {
|
||||
return val.unmarkForce().IsNull()
|
||||
}
|
||||
return val.v == nil
|
||||
}
|
||||
|
||||
|
@ -74,6 +80,10 @@ var NilVal = Value{
|
|||
// inside collections and structures to see if there are any nested unknown
|
||||
// values.
|
||||
func (val Value) IsWhollyKnown() bool {
|
||||
if val.IsMarked() {
|
||||
return val.unmarkForce().IsWhollyKnown()
|
||||
}
|
||||
|
||||
if !val.IsKnown() {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -240,8 +240,18 @@ func SetVal(vals []Value) Value {
|
|||
}
|
||||
elementType := DynamicPseudoType
|
||||
rawList := make([]interface{}, len(vals))
|
||||
var markSets []ValueMarks
|
||||
|
||||
for i, val := range vals {
|
||||
if unmarkedVal, marks := val.UnmarkDeep(); len(marks) > 0 {
|
||||
val = unmarkedVal
|
||||
markSets = append(markSets, marks)
|
||||
}
|
||||
if val.ContainsMarked() {
|
||||
// FIXME: Allow this, but unmark the values and apply the
|
||||
// marking to the set itself instead.
|
||||
panic("set cannot contain marked values")
|
||||
}
|
||||
if elementType == DynamicPseudoType {
|
||||
elementType = val.ty
|
||||
} else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) {
|
||||
|
@ -259,7 +269,7 @@ func SetVal(vals []Value) Value {
|
|||
return Value{
|
||||
ty: Set(elementType),
|
||||
v: rawVal,
|
||||
}
|
||||
}.WithMarks(markSets...)
|
||||
}
|
||||
|
||||
// SetValFromValueSet returns a Value of set type based on an already-constructed
|
||||
|
|
|
@ -11,6 +11,18 @@ import (
|
|||
// GoString is an implementation of fmt.GoStringer that produces concise
|
||||
// source-like representations of values suitable for use in debug messages.
|
||||
func (val Value) GoString() string {
|
||||
if val.IsMarked() {
|
||||
unVal, marks := val.Unmark()
|
||||
if len(marks) == 1 {
|
||||
var mark interface{}
|
||||
for m := range marks {
|
||||
mark = m
|
||||
}
|
||||
return fmt.Sprintf("%#v.Mark(%#v)", unVal, mark)
|
||||
}
|
||||
return fmt.Sprintf("%#v.WithMarks(%#v)", unVal, marks)
|
||||
}
|
||||
|
||||
if val == NilVal {
|
||||
return "cty.NilVal"
|
||||
}
|
||||
|
@ -82,8 +94,12 @@ func (val Value) GoString() string {
|
|||
vals := val.AsValueMap()
|
||||
return fmt.Sprintf("cty.ObjectVal(%#v)", vals)
|
||||
case val.ty.IsCapsuleType():
|
||||
impl := val.ty.CapsuleOps().GoString
|
||||
if impl == nil {
|
||||
return fmt.Sprintf("cty.CapsuleVal(%#v, %#v)", val.ty, val.v)
|
||||
}
|
||||
return impl(val.EncapsulatedValue())
|
||||
}
|
||||
|
||||
// Default exposes implementation details, so should actually cover
|
||||
// all of the cases above for good caller UX.
|
||||
|
@ -101,6 +117,12 @@ func (val Value) GoString() string {
|
|||
// Use RawEquals to compare if two values are equal *ignoring* the
|
||||
// short-circuit rules and the exception for null values.
|
||||
func (val Value) Equals(other Value) Value {
|
||||
if val.IsMarked() || other.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
other, otherMarks := other.Unmark()
|
||||
return val.Equals(other).WithMarks(valMarks, otherMarks)
|
||||
}
|
||||
|
||||
// Start by handling Unknown values before considering types.
|
||||
// This needs to be done since Null values are always equal regardless of
|
||||
// type.
|
||||
|
@ -288,10 +310,22 @@ func (val Value) Equals(other Value) Value {
|
|||
}
|
||||
}
|
||||
case ty.IsCapsuleType():
|
||||
impl := val.ty.CapsuleOps().Equals
|
||||
if impl == nil {
|
||||
impl := val.ty.CapsuleOps().RawEquals
|
||||
if impl == nil {
|
||||
// A capsule type's encapsulated value is a pointer to a value of its
|
||||
// native type, so we can just compare these to get the identity test
|
||||
// we need.
|
||||
return BoolVal(val.v == other.v)
|
||||
}
|
||||
return BoolVal(impl(val.v, other.v))
|
||||
}
|
||||
ret := impl(val.v, other.v)
|
||||
if !ret.Type().Equals(Bool) {
|
||||
panic(fmt.Sprintf("Equals for %#v returned %#v, not cty.Bool", ty, ret.Type()))
|
||||
}
|
||||
return ret
|
||||
|
||||
default:
|
||||
// should never happen
|
||||
|
@ -314,6 +348,7 @@ func (val Value) NotEqual(other Value) Value {
|
|||
// or null values. For more robust handling with unknown value
|
||||
// short-circuiting, use val.Equals(cty.True).
|
||||
func (val Value) True() bool {
|
||||
val.assertUnmarked()
|
||||
if val.ty != Bool {
|
||||
panic("not bool")
|
||||
}
|
||||
|
@ -338,6 +373,13 @@ func (val Value) RawEquals(other Value) bool {
|
|||
if !val.ty.Equals(other.ty) {
|
||||
return false
|
||||
}
|
||||
if !val.HasSameMarks(other) {
|
||||
return false
|
||||
}
|
||||
// Since we've now checked the marks, we'll unmark for the rest of this...
|
||||
val = val.unmarkForce()
|
||||
other = other.unmarkForce()
|
||||
|
||||
if (!val.IsKnown()) && (!other.IsKnown()) {
|
||||
return true
|
||||
}
|
||||
|
@ -448,10 +490,14 @@ func (val Value) RawEquals(other Value) bool {
|
|||
}
|
||||
return false
|
||||
case ty.IsCapsuleType():
|
||||
impl := val.ty.CapsuleOps().RawEquals
|
||||
if impl == nil {
|
||||
// A capsule type's encapsulated value is a pointer to a value of its
|
||||
// native type, so we can just compare these to get the identity test
|
||||
// we need.
|
||||
return val.v == other.v
|
||||
}
|
||||
return impl(val.v, other.v)
|
||||
|
||||
default:
|
||||
// should never happen
|
||||
|
@ -462,6 +508,12 @@ func (val Value) RawEquals(other Value) bool {
|
|||
// Add returns the sum of the receiver and the given other value. Both values
|
||||
// must be numbers; this method will panic if not.
|
||||
func (val Value) Add(other Value) Value {
|
||||
if val.IsMarked() || other.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
other, otherMarks := other.Unmark()
|
||||
return val.Add(other).WithMarks(valMarks, otherMarks)
|
||||
}
|
||||
|
||||
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
|
||||
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
||||
return *shortCircuit
|
||||
|
@ -475,6 +527,12 @@ func (val Value) Add(other Value) Value {
|
|||
// Subtract returns receiver minus the given other value. Both values must be
|
||||
// numbers; this method will panic if not.
|
||||
func (val Value) Subtract(other Value) Value {
|
||||
if val.IsMarked() || other.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
other, otherMarks := other.Unmark()
|
||||
return val.Subtract(other).WithMarks(valMarks, otherMarks)
|
||||
}
|
||||
|
||||
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
|
||||
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
||||
return *shortCircuit
|
||||
|
@ -486,6 +544,11 @@ func (val Value) Subtract(other Value) Value {
|
|||
// Negate returns the numeric negative of the receiver, which must be a number.
|
||||
// This method will panic when given a value of any other type.
|
||||
func (val Value) Negate() Value {
|
||||
if val.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
return val.Negate().WithMarks(valMarks)
|
||||
}
|
||||
|
||||
if shortCircuit := mustTypeCheck(Number, Number, val); shortCircuit != nil {
|
||||
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
||||
return *shortCircuit
|
||||
|
@ -498,6 +561,12 @@ func (val Value) Negate() Value {
|
|||
// Multiply returns the product of the receiver and the given other value.
|
||||
// Both values must be numbers; this method will panic if not.
|
||||
func (val Value) Multiply(other Value) Value {
|
||||
if val.IsMarked() || other.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
other, otherMarks := other.Unmark()
|
||||
return val.Multiply(other).WithMarks(valMarks, otherMarks)
|
||||
}
|
||||
|
||||
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
|
||||
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
||||
return *shortCircuit
|
||||
|
@ -520,6 +589,12 @@ func (val Value) Multiply(other Value) Value {
|
|||
// If both values are zero or infinity, this function will panic with
|
||||
// an instance of big.ErrNaN.
|
||||
func (val Value) Divide(other Value) Value {
|
||||
if val.IsMarked() || other.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
other, otherMarks := other.Unmark()
|
||||
return val.Divide(other).WithMarks(valMarks, otherMarks)
|
||||
}
|
||||
|
||||
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
|
||||
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
||||
return *shortCircuit
|
||||
|
@ -546,6 +621,12 @@ func (val Value) Divide(other Value) Value {
|
|||
// may wish to disallow such things outright or implement their own modulo
|
||||
// if they disagree with the interpretation used here.
|
||||
func (val Value) Modulo(other Value) Value {
|
||||
if val.IsMarked() || other.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
other, otherMarks := other.Unmark()
|
||||
return val.Modulo(other).WithMarks(valMarks, otherMarks)
|
||||
}
|
||||
|
||||
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
|
||||
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
||||
return *shortCircuit
|
||||
|
@ -576,6 +657,11 @@ func (val Value) Modulo(other Value) Value {
|
|||
// Absolute returns the absolute (signless) value of the receiver, which must
|
||||
// be a number or this method will panic.
|
||||
func (val Value) Absolute() Value {
|
||||
if val.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
return val.Absolute().WithMarks(valMarks)
|
||||
}
|
||||
|
||||
if shortCircuit := mustTypeCheck(Number, Number, val); shortCircuit != nil {
|
||||
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
||||
return *shortCircuit
|
||||
|
@ -596,6 +682,11 @@ func (val Value) Absolute() Value {
|
|||
// This method may be called on a value whose type is DynamicPseudoType,
|
||||
// in which case the result will also be DynamicVal.
|
||||
func (val Value) GetAttr(name string) Value {
|
||||
if val.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
return val.GetAttr(name).WithMarks(valMarks)
|
||||
}
|
||||
|
||||
if val.ty == DynamicPseudoType {
|
||||
return DynamicVal
|
||||
}
|
||||
|
@ -638,6 +729,12 @@ func (val Value) GetAttr(name string) Value {
|
|||
// This method may be called on a value whose type is DynamicPseudoType,
|
||||
// in which case the result will also be the DynamicValue.
|
||||
func (val Value) Index(key Value) Value {
|
||||
if val.IsMarked() || key.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
key, keyMarks := key.Unmark()
|
||||
return val.Index(key).WithMarks(valMarks, keyMarks)
|
||||
}
|
||||
|
||||
if val.ty == DynamicPseudoType {
|
||||
return DynamicVal
|
||||
}
|
||||
|
@ -733,6 +830,12 @@ func (val Value) Index(key Value) Value {
|
|||
// This method will panic if the receiver is not indexable, but does not
|
||||
// impose any panic-causing type constraints on the key.
|
||||
func (val Value) HasIndex(key Value) Value {
|
||||
if val.IsMarked() || key.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
key, keyMarks := key.Unmark()
|
||||
return val.HasIndex(key).WithMarks(valMarks, keyMarks)
|
||||
}
|
||||
|
||||
if val.ty == DynamicPseudoType {
|
||||
return UnknownVal(Bool)
|
||||
}
|
||||
|
@ -810,6 +913,12 @@ func (val Value) HasIndex(key Value) Value {
|
|||
//
|
||||
// This method will panic if the receiver is not a set, or if it is a null set.
|
||||
func (val Value) HasElement(elem Value) Value {
|
||||
if val.IsMarked() || elem.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
elem, elemMarks := elem.Unmark()
|
||||
return val.HasElement(elem).WithMarks(valMarks, elemMarks)
|
||||
}
|
||||
|
||||
ty := val.Type()
|
||||
|
||||
if !ty.IsSetType() {
|
||||
|
@ -841,6 +950,11 @@ func (val Value) HasElement(elem Value) Value {
|
|||
// of a string, call AsString and take the length of the native Go string
|
||||
// that is returned.
|
||||
func (val Value) Length() Value {
|
||||
if val.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
return val.Length().WithMarks(valMarks)
|
||||
}
|
||||
|
||||
if val.Type().IsTupleType() {
|
||||
// For tuples, we can return the length even if the value is not known.
|
||||
return NumberIntVal(int64(val.Type().Length()))
|
||||
|
@ -859,6 +973,7 @@ func (val Value) Length() Value {
|
|||
// This is an integration method provided for the convenience of code bridging
|
||||
// into Go's type system.
|
||||
func (val Value) LengthInt() int {
|
||||
val.assertUnmarked()
|
||||
if val.Type().IsTupleType() {
|
||||
// For tuples, we can return the length even if the value is not known.
|
||||
return val.Type().Length()
|
||||
|
@ -915,6 +1030,7 @@ func (val Value) LengthInt() int {
|
|||
// ElementIterator is an integration method, so it cannot handle Unknown
|
||||
// values. This method will panic if the receiver is Unknown.
|
||||
func (val Value) ElementIterator() ElementIterator {
|
||||
val.assertUnmarked()
|
||||
if !val.IsKnown() {
|
||||
panic("can't use ElementIterator on unknown value")
|
||||
}
|
||||
|
@ -943,6 +1059,7 @@ func (val Value) CanIterateElements() bool {
|
|||
// ForEachElement is an integration method, so it cannot handle Unknown
|
||||
// values. This method will panic if the receiver is Unknown.
|
||||
func (val Value) ForEachElement(cb ElementCallback) bool {
|
||||
val.assertUnmarked()
|
||||
it := val.ElementIterator()
|
||||
for it.Next() {
|
||||
key, val := it.Element()
|
||||
|
@ -957,6 +1074,11 @@ func (val Value) ForEachElement(cb ElementCallback) bool {
|
|||
// Not returns the logical inverse of the receiver, which must be of type
|
||||
// Bool or this method will panic.
|
||||
func (val Value) Not() Value {
|
||||
if val.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
return val.Not().WithMarks(valMarks)
|
||||
}
|
||||
|
||||
if shortCircuit := mustTypeCheck(Bool, Bool, val); shortCircuit != nil {
|
||||
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
||||
return *shortCircuit
|
||||
|
@ -968,6 +1090,12 @@ func (val Value) Not() Value {
|
|||
// And returns the result of logical AND with the receiver and the other given
|
||||
// value, which must both be of type Bool or this method will panic.
|
||||
func (val Value) And(other Value) Value {
|
||||
if val.IsMarked() || other.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
other, otherMarks := other.Unmark()
|
||||
return val.And(other).WithMarks(valMarks, otherMarks)
|
||||
}
|
||||
|
||||
if shortCircuit := mustTypeCheck(Bool, Bool, val, other); shortCircuit != nil {
|
||||
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
||||
return *shortCircuit
|
||||
|
@ -979,6 +1107,12 @@ func (val Value) And(other Value) Value {
|
|||
// Or returns the result of logical OR with the receiver and the other given
|
||||
// value, which must both be of type Bool or this method will panic.
|
||||
func (val Value) Or(other Value) Value {
|
||||
if val.IsMarked() || other.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
other, otherMarks := other.Unmark()
|
||||
return val.Or(other).WithMarks(valMarks, otherMarks)
|
||||
}
|
||||
|
||||
if shortCircuit := mustTypeCheck(Bool, Bool, val, other); shortCircuit != nil {
|
||||
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
||||
return *shortCircuit
|
||||
|
@ -990,6 +1124,12 @@ func (val Value) Or(other Value) Value {
|
|||
// LessThan returns True if the receiver is less than the other given value,
|
||||
// which must both be numbers or this method will panic.
|
||||
func (val Value) LessThan(other Value) Value {
|
||||
if val.IsMarked() || other.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
other, otherMarks := other.Unmark()
|
||||
return val.LessThan(other).WithMarks(valMarks, otherMarks)
|
||||
}
|
||||
|
||||
if shortCircuit := mustTypeCheck(Number, Bool, val, other); shortCircuit != nil {
|
||||
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
||||
return *shortCircuit
|
||||
|
@ -1001,6 +1141,12 @@ func (val Value) LessThan(other Value) Value {
|
|||
// GreaterThan returns True if the receiver is greater than the other given
|
||||
// value, which must both be numbers or this method will panic.
|
||||
func (val Value) GreaterThan(other Value) Value {
|
||||
if val.IsMarked() || other.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
other, otherMarks := other.Unmark()
|
||||
return val.GreaterThan(other).WithMarks(valMarks, otherMarks)
|
||||
}
|
||||
|
||||
if shortCircuit := mustTypeCheck(Number, Bool, val, other); shortCircuit != nil {
|
||||
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
||||
return *shortCircuit
|
||||
|
@ -1022,6 +1168,7 @@ func (val Value) GreaterThanOrEqualTo(other Value) Value {
|
|||
// AsString returns the native string from a non-null, non-unknown cty.String
|
||||
// value, or panics if called on any other value.
|
||||
func (val Value) AsString() string {
|
||||
val.assertUnmarked()
|
||||
if val.ty != String {
|
||||
panic("not a string")
|
||||
}
|
||||
|
@ -1041,6 +1188,7 @@ func (val Value) AsString() string {
|
|||
// For more convenient conversions to other native numeric types, use the
|
||||
// "gocty" package.
|
||||
func (val Value) AsBigFloat() *big.Float {
|
||||
val.assertUnmarked()
|
||||
if val.ty != Number {
|
||||
panic("not a number")
|
||||
}
|
||||
|
@ -1064,6 +1212,7 @@ func (val Value) AsBigFloat() *big.Float {
|
|||
// For more convenient conversions to slices of more specific types, use
|
||||
// the "gocty" package.
|
||||
func (val Value) AsValueSlice() []Value {
|
||||
val.assertUnmarked()
|
||||
l := val.LengthInt()
|
||||
if l == 0 {
|
||||
return nil
|
||||
|
@ -1084,6 +1233,7 @@ func (val Value) AsValueSlice() []Value {
|
|||
// For more convenient conversions to maps of more specific types, use
|
||||
// the "gocty" package.
|
||||
func (val Value) AsValueMap() map[string]Value {
|
||||
val.assertUnmarked()
|
||||
l := val.LengthInt()
|
||||
if l == 0 {
|
||||
return nil
|
||||
|
@ -1108,6 +1258,7 @@ func (val Value) AsValueMap() map[string]Value {
|
|||
//
|
||||
// The returned ValueSet can store only values of the receiver's element type.
|
||||
func (val Value) AsValueSet() ValueSet {
|
||||
val.assertUnmarked()
|
||||
if !val.Type().IsCollectionType() {
|
||||
panic("not a collection type")
|
||||
}
|
||||
|
@ -1130,6 +1281,7 @@ func (val Value) AsValueSet() ValueSet {
|
|||
// the value. Since cty considers values to be immutable, it is strongly
|
||||
// recommended to treat the encapsulated value itself as immutable too.
|
||||
func (val Value) EncapsulatedValue() interface{} {
|
||||
val.assertUnmarked()
|
||||
if !val.Type().IsCapsuleType() {
|
||||
panic("not a capsule-typed value")
|
||||
}
|
||||
|
|
|
@ -349,7 +349,7 @@ github.com/hashicorp/hcl/hcl/scanner
|
|||
github.com/hashicorp/hcl/hcl/strconv
|
||||
github.com/hashicorp/hcl/json/scanner
|
||||
github.com/hashicorp/hcl/json/token
|
||||
# github.com/hashicorp/hcl/v2 v2.2.0
|
||||
# github.com/hashicorp/hcl/v2 v2.3.0
|
||||
github.com/hashicorp/hcl/v2
|
||||
github.com/hashicorp/hcl/v2/hclsyntax
|
||||
github.com/hashicorp/hcl/v2/hcldec
|
||||
|
@ -360,6 +360,7 @@ github.com/hashicorp/hcl/v2/hclparse
|
|||
github.com/hashicorp/hcl/v2/gohcl
|
||||
github.com/hashicorp/hcl/v2/ext/typeexpr
|
||||
github.com/hashicorp/hcl/v2/ext/dynblock
|
||||
github.com/hashicorp/hcl/v2/ext/customdecode
|
||||
github.com/hashicorp/hcl/v2/hcltest
|
||||
# github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590
|
||||
github.com/hashicorp/hil
|
||||
|
@ -484,7 +485,7 @@ github.com/vmihailenco/msgpack/codes
|
|||
github.com/xanzy/ssh-agent
|
||||
# github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557
|
||||
github.com/xlab/treeprint
|
||||
# github.com/zclconf/go-cty v1.1.1
|
||||
# github.com/zclconf/go-cty v1.2.1
|
||||
github.com/zclconf/go-cty/cty
|
||||
github.com/zclconf/go-cty/cty/gocty
|
||||
github.com/zclconf/go-cty/cty/convert
|
||||
|
|
Loading…
Reference in New Issue