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-uuid v1.0.1
|
||||||
github.com/hashicorp/go-version v1.2.0
|
github.com/hashicorp/go-version v1.2.0
|
||||||
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f
|
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/hil v0.0.0-20190212112733-ab17b08d6590
|
||||||
github.com/hashicorp/memberlist v0.1.0 // indirect
|
github.com/hashicorp/memberlist v0.1.0 // indirect
|
||||||
github.com/hashicorp/serf v0.0.0-20160124182025-e4ec8cc423bb // 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/xanzy/ssh-agent v0.2.1
|
||||||
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18 // indirect
|
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18 // indirect
|
||||||
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557
|
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
|
github.com/zclconf/go-cty-yaml v1.0.1
|
||||||
go.uber.org/atomic v1.3.2 // indirect
|
go.uber.org/atomic v1.3.2 // indirect
|
||||||
go.uber.org/multierr v1.1.0 // 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 h1:UdxlrJz4JOnY8W+DbLISwf2B8WXEolNRA8BGCwI9jws=
|
||||||
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
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.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90=
|
||||||
github.com/hashicorp/hcl/v2 v2.2.0 h1:ZQ1eNLggMfTyFBhV8swxT081mlaRjr4EG85NEjjLB84=
|
github.com/hashicorp/hcl/v2 v2.3.0 h1:iRly8YaMwTBAKhn1Ybk7VSdzbnopghktCD031P8ggUE=
|
||||||
github.com/hashicorp/hcl/v2 v2.2.0/go.mod h1:MD4q2LOluJ5pRwTVkCXmJOY7ODWDXVXGVB8LY0t7wig=
|
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 h1:2yzhWGdgQUWZUCNK+AoO35V+HTsgEmcM4J9IkArh7PI=
|
||||||
github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590/go.mod h1:n2TSygSNwsLJ76m8qFXTSc7beTb+auJxYdqrnoqwZWE=
|
github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590/go.mod h1:n2TSygSNwsLJ76m8qFXTSc7beTb+auJxYdqrnoqwZWE=
|
||||||
github.com/hashicorp/memberlist v0.1.0 h1:qSsCiC0WYD39lbSitKNt40e30uorm2Ss/d4JGU1hzH8=
|
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.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 h1:uJwc9HiBOCpoKIObTQaLR+tsEXx1HBHnOsOOpcdhZgw=
|
||||||
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
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.2.0 h1:sPHsy7ADcIZQP3vILvTjrh74ZA175TFP5vqiNK1UmlI=
|
||||||
github.com/zclconf/go-cty v1.1.1/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
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 h1:up11wlgAaDvlAGENcFDnZgkn0qUJurso7k6EpURKNF8=
|
||||||
github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
|
github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
# HCL Changelog
|
# 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)
|
## v2.2.0 (Dec 11, 2019)
|
||||||
|
|
||||||
### Enhancements
|
### 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
|
types with weird attributes generally show up only from arbitrary object
|
||||||
constructors in configuration files, which are usually treated either as maps
|
constructors in configuration files, which are usually treated either as maps
|
||||||
or as the dynamic pseudo-type.
|
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/apparentlymart/go-textseg v1.0.0
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/go-test/deep v1.0.3
|
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/kr/pretty v0.1.0
|
||||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348
|
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348
|
||||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7
|
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/sergi/go-diff v1.0.0
|
||||||
github.com/spf13/pflag v1.0.2
|
github.com/spf13/pflag v1.0.2
|
||||||
github.com/stretchr/testify v1.2.2 // indirect
|
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/crypto v0.0.0-20190426145343-a29dc8fdc734
|
||||||
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82 // indirect
|
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82 // indirect
|
||||||
golang.org/x/text v0.3.2 // 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 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||||
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
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/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.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
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 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
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/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.2.0 h1:sPHsy7ADcIZQP3vILvTjrh74ZA175TFP5vqiNK1UmlI=
|
||||||
github.com/zclconf/go-cty v1.1.1/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
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-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 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo=
|
||||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
"github.com/hashicorp/hcl/v2/ext/customdecode"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
"github.com/zclconf/go-cty/cty/convert"
|
"github.com/zclconf/go-cty/cty/convert"
|
||||||
"github.com/zclconf/go-cty/cty/function"
|
"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
|
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)
|
val, diags := attr.Expr.Value(ctx)
|
||||||
|
|
||||||
convVal, err := convert.Convert(val, s.Type)
|
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))
|
vals := make(map[string]cty.Value, len(attrs))
|
||||||
for name, attr := range 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)
|
attrVal, attrDiags := attr.Expr.Value(ctx)
|
||||||
diags = append(diags, attrDiags...)
|
diags = append(diags, attrDiags...)
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
"github.com/hashicorp/hcl/v2/ext/customdecode"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
"github.com/zclconf/go-cty/cty/convert"
|
"github.com/zclconf/go-cty/cty/convert"
|
||||||
"github.com/zclconf/go-cty/cty/function"
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
|
@ -350,26 +351,38 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
|
||||||
param = varParam
|
param = varParam
|
||||||
}
|
}
|
||||||
|
|
||||||
val, argDiags := argExpr.Value(ctx)
|
var val cty.Value
|
||||||
if len(argDiags) > 0 {
|
if decodeFn := customdecode.CustomExpressionDecoderForType(param.Type); decodeFn != nil {
|
||||||
|
var argDiags hcl.Diagnostics
|
||||||
|
val, argDiags = decodeFn(argExpr, ctx)
|
||||||
diags = append(diags, argDiags...)
|
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
|
// Try to convert our value to the parameter type
|
||||||
val, err := convert.Convert(val, param.Type)
|
var err error
|
||||||
if err != nil {
|
val, err = convert.Convert(val, param.Type)
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
if err != nil {
|
||||||
Severity: hcl.DiagError,
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
Summary: "Invalid function argument",
|
Severity: hcl.DiagError,
|
||||||
Detail: fmt.Sprintf(
|
Summary: "Invalid function argument",
|
||||||
"Invalid value for %q parameter: %s.",
|
Detail: fmt.Sprintf(
|
||||||
param.Name, err,
|
"Invalid value for %q parameter: %s.",
|
||||||
),
|
param.Name, err,
|
||||||
Subject: argExpr.StartRange().Ptr(),
|
),
|
||||||
Context: e.Range().Ptr(),
|
Subject: argExpr.StartRange().Ptr(),
|
||||||
Expression: argExpr,
|
Context: e.Range().Ptr(),
|
||||||
EvalContext: ctx,
|
Expression: argExpr,
|
||||||
})
|
EvalContext: ctx,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
argVals[i] = val
|
argVals[i] = val
|
||||||
|
|
|
@ -9,6 +9,7 @@ type capsuleType struct {
|
||||||
typeImplSigil
|
typeImplSigil
|
||||||
Name string
|
Name string
|
||||||
GoType reflect.Type
|
GoType reflect.Type
|
||||||
|
Ops *CapsuleOps
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *capsuleType) Equals(other Type) bool {
|
func (t *capsuleType) Equals(other Type) bool {
|
||||||
|
@ -24,10 +25,22 @@ func (t *capsuleType) FriendlyName(mode friendlyTypeNameMode) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *capsuleType) GoString() string {
|
func (t *capsuleType) GoString() string {
|
||||||
// To get a useful representation of our native type requires some
|
impl := t.Ops.TypeGoString
|
||||||
// shenanigans.
|
if impl == nil {
|
||||||
victimVal := reflect.Zero(t.GoType)
|
// To get a useful representation of our native type requires some
|
||||||
return fmt.Sprintf("cty.Capsule(%q, reflect.TypeOf(%#v))", t.Name, victimVal.Interface())
|
// 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.
|
// Capsule creates a new Capsule type.
|
||||||
|
@ -47,8 +60,11 @@ func (t *capsuleType) GoString() string {
|
||||||
// use the same native type.
|
// use the same native type.
|
||||||
//
|
//
|
||||||
// Each capsule-typed value contains a pointer to a value of the given native
|
// 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
|
// type. A capsule-typed value by default supports no operations except
|
||||||
// equality is implemented by pointer identity of the encapsulated pointer.
|
// 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
|
// 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
|
// 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{
|
&capsuleType{
|
||||||
Name: name,
|
Name: name,
|
||||||
GoType: nativeType,
|
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
|
// Wrap the conversion in some standard checks that we don't want to
|
||||||
// have to repeat in every conversion function.
|
// 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 {
|
if out == cty.DynamicPseudoType {
|
||||||
// Conversion to DynamicPseudoType always just passes through verbatim.
|
// Conversion to DynamicPseudoType always just passes through verbatim.
|
||||||
return in, nil
|
return in, nil
|
||||||
|
@ -33,6 +45,8 @@ func getConversion(in cty.Type, out cty.Type, unsafe bool) conversion {
|
||||||
|
|
||||||
return conv(in, path)
|
return conv(in, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConversionKnown(in cty.Type, out cty.Type, unsafe bool) conversion {
|
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()
|
outEty := out.ElementType()
|
||||||
return conversionObjectToMap(in, outEty, unsafe)
|
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:
|
default:
|
||||||
return nil
|
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 {
|
func canElementIterator(val Value) bool {
|
||||||
switch {
|
switch {
|
||||||
|
case val.IsMarked():
|
||||||
|
return false
|
||||||
case val.ty.IsListType():
|
case val.ty.IsListType():
|
||||||
return true
|
return true
|
||||||
case val.ty.IsMapType():
|
case val.ty.IsMapType():
|
||||||
|
@ -39,6 +41,7 @@ func canElementIterator(val Value) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func elementIterator(val Value) ElementIterator {
|
func elementIterator(val Value) ElementIterator {
|
||||||
|
val.assertUnmarked()
|
||||||
switch {
|
switch {
|
||||||
case val.ty.IsListType():
|
case val.ty.IsListType():
|
||||||
return &listElementIterator{
|
return &listElementIterator{
|
||||||
|
|
|
@ -47,4 +47,24 @@ type Parameter struct {
|
||||||
// values are not, thus improving the type-check accuracy of derived
|
// values are not, thus improving the type-check accuracy of derived
|
||||||
// values.
|
// values.
|
||||||
AllowDynamicType bool
|
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 {
|
for i, spec := range f.spec.Params {
|
||||||
val := posArgs[i]
|
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 {
|
if val.IsNull() && !spec.AllowNull {
|
||||||
return cty.Type{}, NewArgErrorf(i, "argument must not be null")
|
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 {
|
for i, val := range varArgs {
|
||||||
realI := i + len(posArgs)
|
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 {
|
if val.IsNull() && !spec.AllowNull {
|
||||||
return cty.Type{}, NewArgErrorf(realI, "argument must not be null")
|
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
|
// Type checking already dealt with most situations relating to our
|
||||||
// parameter specification, but we still need to deal with unknown
|
// parameter specification, but we still need to deal with unknown
|
||||||
// values.
|
// values and marked values.
|
||||||
posArgs := args[:len(f.spec.Params)]
|
posArgs := args[:len(f.spec.Params)]
|
||||||
varArgs := args[len(f.spec.Params):]
|
varArgs := args[len(f.spec.Params):]
|
||||||
|
var resultMarks []cty.ValueMarks
|
||||||
|
|
||||||
for i, spec := range f.spec.Params {
|
for i, spec := range f.spec.Params {
|
||||||
val := posArgs[i]
|
val := posArgs[i]
|
||||||
|
@ -218,14 +243,37 @@ func (f Function) Call(args []cty.Value) (val cty.Value, err error) {
|
||||||
if !val.IsKnown() && !spec.AllowUnknown {
|
if !val.IsKnown() && !spec.AllowUnknown {
|
||||||
return cty.UnknownVal(expectedType), nil
|
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 {
|
if f.spec.VarParam != nil {
|
||||||
spec := f.spec.VarParam
|
spec := f.spec.VarParam
|
||||||
for _, val := range varArgs {
|
for i, val := range varArgs {
|
||||||
if !val.IsKnown() && !spec.AllowUnknown {
|
if !val.IsKnown() && !spec.AllowUnknown {
|
||||||
return cty.UnknownVal(expectedType), nil
|
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 {
|
if err != nil {
|
||||||
return cty.NilVal, err
|
return cty.NilVal, err
|
||||||
}
|
}
|
||||||
|
if len(resultMarks) > 0 {
|
||||||
|
retVal = retVal.WithMarks(resultMarks...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returned value must conform to what the Type function expected, to
|
// Returned value must conform to what the Type function expected, to
|
||||||
|
|
|
@ -11,6 +11,7 @@ var NotFunc = function.New(&function.Spec{
|
||||||
Name: "val",
|
Name: "val",
|
||||||
Type: cty.Bool,
|
Type: cty.Bool,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Type: function.StaticReturnType(cty.Bool),
|
Type: function.StaticReturnType(cty.Bool),
|
||||||
|
@ -25,11 +26,13 @@ var AndFunc = function.New(&function.Spec{
|
||||||
Name: "a",
|
Name: "a",
|
||||||
Type: cty.Bool,
|
Type: cty.Bool,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "b",
|
Name: "b",
|
||||||
Type: cty.Bool,
|
Type: cty.Bool,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Type: function.StaticReturnType(cty.Bool),
|
Type: function.StaticReturnType(cty.Bool),
|
||||||
|
@ -44,11 +47,13 @@ var OrFunc = function.New(&function.Spec{
|
||||||
Name: "a",
|
Name: "a",
|
||||||
Type: cty.Bool,
|
Type: cty.Bool,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "b",
|
Name: "b",
|
||||||
Type: cty.Bool,
|
Type: cty.Bool,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Type: function.StaticReturnType(cty.Bool),
|
Type: function.StaticReturnType(cty.Bool),
|
||||||
|
|
|
@ -14,6 +14,7 @@ var AbsoluteFunc = function.New(&function.Spec{
|
||||||
Name: "num",
|
Name: "num",
|
||||||
Type: cty.Number,
|
Type: cty.Number,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Type: function.StaticReturnType(cty.Number),
|
Type: function.StaticReturnType(cty.Number),
|
||||||
|
@ -196,11 +197,13 @@ var GreaterThanFunc = function.New(&function.Spec{
|
||||||
Name: "a",
|
Name: "a",
|
||||||
Type: cty.Number,
|
Type: cty.Number,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "b",
|
Name: "b",
|
||||||
Type: cty.Number,
|
Type: cty.Number,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Type: function.StaticReturnType(cty.Bool),
|
Type: function.StaticReturnType(cty.Bool),
|
||||||
|
@ -215,11 +218,13 @@ var GreaterThanOrEqualToFunc = function.New(&function.Spec{
|
||||||
Name: "a",
|
Name: "a",
|
||||||
Type: cty.Number,
|
Type: cty.Number,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "b",
|
Name: "b",
|
||||||
Type: cty.Number,
|
Type: cty.Number,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Type: function.StaticReturnType(cty.Bool),
|
Type: function.StaticReturnType(cty.Bool),
|
||||||
|
@ -234,11 +239,13 @@ var LessThanFunc = function.New(&function.Spec{
|
||||||
Name: "a",
|
Name: "a",
|
||||||
Type: cty.Number,
|
Type: cty.Number,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "b",
|
Name: "b",
|
||||||
Type: cty.Number,
|
Type: cty.Number,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Type: function.StaticReturnType(cty.Bool),
|
Type: function.StaticReturnType(cty.Bool),
|
||||||
|
@ -253,11 +260,13 @@ var LessThanOrEqualToFunc = function.New(&function.Spec{
|
||||||
Name: "a",
|
Name: "a",
|
||||||
Type: cty.Number,
|
Type: cty.Number,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "b",
|
Name: "b",
|
||||||
Type: cty.Number,
|
Type: cty.Number,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Type: function.StaticReturnType(cty.Bool),
|
Type: function.StaticReturnType(cty.Bool),
|
||||||
|
@ -272,6 +281,7 @@ var NegateFunc = function.New(&function.Spec{
|
||||||
Name: "num",
|
Name: "num",
|
||||||
Type: cty.Number,
|
Type: cty.Number,
|
||||||
AllowDynamicType: true,
|
AllowDynamicType: true,
|
||||||
|
AllowMarked: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Type: function.StaticReturnType(cty.Number),
|
Type: function.StaticReturnType(cty.Number),
|
||||||
|
|
|
@ -3,10 +3,10 @@ package stdlib
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/apparentlymart/go-textseg/textseg"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
"github.com/zclconf/go-cty/cty/function"
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
"github.com/zclconf/go-cty/cty/gocty"
|
"github.com/zclconf/go-cty/cty/gocty"
|
||||||
"github.com/apparentlymart/go-textseg/textseg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var UpperFunc = function.New(&function.Spec{
|
var UpperFunc = function.New(&function.Spec{
|
||||||
|
|
|
@ -3,6 +3,7 @@ package cty
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
@ -15,6 +16,10 @@ import (
|
||||||
// Currently it is not possible to represent values of capsule types in gob,
|
// Currently it is not possible to represent values of capsule types in gob,
|
||||||
// because the types themselves cannot be represented.
|
// because the types themselves cannot be represented.
|
||||||
func (val Value) GobEncode() ([]byte, error) {
|
func (val Value) GobEncode() ([]byte, error) {
|
||||||
|
if val.IsMarked() {
|
||||||
|
return nil, errors.New("value is marked")
|
||||||
|
}
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
enc := gob.NewEncoder(buf)
|
enc := gob.NewEncoder(buf)
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func marshal(val cty.Value, t cty.Type, path cty.Path, b *bytes.Buffer) error {
|
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
|
// If we're going to decode as DynamicPseudoType then we need to save
|
||||||
// dynamic type information to recover the real type.
|
// dynamic type information to recover the real type.
|
||||||
if t == cty.DynamicPseudoType && val.Type() != cty.DynamicPseudoType {
|
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 {
|
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
|
// If we're going to decode as DynamicPseudoType then we need to save
|
||||||
// dynamic type information to recover the real type.
|
// dynamic type information to recover the real type.
|
||||||
if ty == cty.DynamicPseudoType && val.Type() != cty.DynamicPseudoType {
|
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.
|
// 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) {
|
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()) {
|
if !v.Type().Equals(s.ElementType()) {
|
||||||
panic(fmt.Errorf("attempt to use %#v value with set of %#v", v.Type(), 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
|
// This function is not safe to use for security-related applications, since
|
||||||
// the hash used is not strong enough.
|
// the hash used is not strong enough.
|
||||||
func (val Value) Hash() int {
|
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))
|
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
|
// default consistent-but-undefined ordering then. This situation is
|
||||||
// not considered a compatibility constraint; callers should rely only
|
// not considered a compatibility constraint; callers should rely only
|
||||||
// on the ordering rules for primitive values.
|
// on the ordering rules for primitive values.
|
||||||
v1h := makeSetHashBytes(v1v)
|
v1h, _ := makeSetHashBytes(v1v)
|
||||||
v2h := makeSetHashBytes(v2v)
|
v2h, _ := makeSetHashBytes(v2v)
|
||||||
return bytes.Compare(v1h, v2h) < 0
|
return bytes.Compare(v1h, v2h) < 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeSetHashBytes(val Value) []byte {
|
func makeSetHashBytes(val Value) ([]byte, ValueMarks) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
appendSetHashBytes(val, &buf)
|
marks := make(ValueMarks)
|
||||||
return buf.Bytes()
|
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
|
// Exactly what bytes we generate here don't matter as long as the following
|
||||||
// constraints hold:
|
// constraints hold:
|
||||||
// - Unknown and null values all generate distinct strings from
|
// - 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
|
// the Equivalent function will still distinguish values, but set
|
||||||
// performance will be best if we are able to produce a distinct string
|
// performance will be best if we are able to produce a distinct string
|
||||||
// for each distinct value, unknown values notwithstanding.
|
// 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() {
|
if !val.IsKnown() {
|
||||||
buf.WriteRune('?')
|
buf.WriteRune('?')
|
||||||
return
|
return
|
||||||
|
@ -175,9 +192,9 @@ func appendSetHashBytes(val Value, buf *bytes.Buffer) {
|
||||||
if val.ty.IsMapType() {
|
if val.ty.IsMapType() {
|
||||||
buf.WriteRune('{')
|
buf.WriteRune('{')
|
||||||
val.ForEachElement(func(keyVal, elementVal Value) bool {
|
val.ForEachElement(func(keyVal, elementVal Value) bool {
|
||||||
appendSetHashBytes(keyVal, buf)
|
appendSetHashBytes(keyVal, buf, marks)
|
||||||
buf.WriteRune(':')
|
buf.WriteRune(':')
|
||||||
appendSetHashBytes(elementVal, buf)
|
appendSetHashBytes(elementVal, buf, marks)
|
||||||
buf.WriteRune(';')
|
buf.WriteRune(';')
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
@ -188,7 +205,7 @@ func appendSetHashBytes(val Value, buf *bytes.Buffer) {
|
||||||
if val.ty.IsListType() || val.ty.IsSetType() {
|
if val.ty.IsListType() || val.ty.IsSetType() {
|
||||||
buf.WriteRune('[')
|
buf.WriteRune('[')
|
||||||
val.ForEachElement(func(keyVal, elementVal Value) bool {
|
val.ForEachElement(func(keyVal, elementVal Value) bool {
|
||||||
appendSetHashBytes(elementVal, buf)
|
appendSetHashBytes(elementVal, buf, marks)
|
||||||
buf.WriteRune(';')
|
buf.WriteRune(';')
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
@ -204,7 +221,7 @@ func appendSetHashBytes(val Value, buf *bytes.Buffer) {
|
||||||
}
|
}
|
||||||
sort.Strings(attrNames)
|
sort.Strings(attrNames)
|
||||||
for _, attrName := range attrNames {
|
for _, attrName := range attrNames {
|
||||||
appendSetHashBytes(val.GetAttr(attrName), buf)
|
appendSetHashBytes(val.GetAttr(attrName), buf, marks)
|
||||||
buf.WriteRune(';')
|
buf.WriteRune(';')
|
||||||
}
|
}
|
||||||
buf.WriteRune('>')
|
buf.WriteRune('>')
|
||||||
|
@ -214,7 +231,7 @@ func appendSetHashBytes(val Value, buf *bytes.Buffer) {
|
||||||
if val.ty.IsTupleType() {
|
if val.ty.IsTupleType() {
|
||||||
buf.WriteRune('<')
|
buf.WriteRune('<')
|
||||||
val.ForEachElement(func(keyVal, elementVal Value) bool {
|
val.ForEachElement(func(keyVal, elementVal Value) bool {
|
||||||
appendSetHashBytes(elementVal, buf)
|
appendSetHashBytes(elementVal, buf, marks)
|
||||||
buf.WriteRune(';')
|
buf.WriteRune(';')
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
|
@ -45,6 +45,9 @@ func (val Value) Type() Type {
|
||||||
// operating on other unknown values, and so an application that never
|
// operating on other unknown values, and so an application that never
|
||||||
// introduces Unknown values can be guaranteed to never receive any either.
|
// introduces Unknown values can be guaranteed to never receive any either.
|
||||||
func (val Value) IsKnown() bool {
|
func (val Value) IsKnown() bool {
|
||||||
|
if val.IsMarked() {
|
||||||
|
return val.unmarkForce().IsKnown()
|
||||||
|
}
|
||||||
return val.v != unknown
|
return val.v != unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +56,9 @@ func (val Value) IsKnown() bool {
|
||||||
// produces null, so an application that never introduces Null values can
|
// produces null, so an application that never introduces Null values can
|
||||||
// be guaranteed to never receive any either.
|
// be guaranteed to never receive any either.
|
||||||
func (val Value) IsNull() bool {
|
func (val Value) IsNull() bool {
|
||||||
|
if val.IsMarked() {
|
||||||
|
return val.unmarkForce().IsNull()
|
||||||
|
}
|
||||||
return val.v == nil
|
return val.v == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +80,10 @@ var NilVal = Value{
|
||||||
// inside collections and structures to see if there are any nested unknown
|
// inside collections and structures to see if there are any nested unknown
|
||||||
// values.
|
// values.
|
||||||
func (val Value) IsWhollyKnown() bool {
|
func (val Value) IsWhollyKnown() bool {
|
||||||
|
if val.IsMarked() {
|
||||||
|
return val.unmarkForce().IsWhollyKnown()
|
||||||
|
}
|
||||||
|
|
||||||
if !val.IsKnown() {
|
if !val.IsKnown() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -240,8 +240,18 @@ func SetVal(vals []Value) Value {
|
||||||
}
|
}
|
||||||
elementType := DynamicPseudoType
|
elementType := DynamicPseudoType
|
||||||
rawList := make([]interface{}, len(vals))
|
rawList := make([]interface{}, len(vals))
|
||||||
|
var markSets []ValueMarks
|
||||||
|
|
||||||
for i, val := range vals {
|
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 {
|
if elementType == DynamicPseudoType {
|
||||||
elementType = val.ty
|
elementType = val.ty
|
||||||
} else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) {
|
} else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) {
|
||||||
|
@ -259,7 +269,7 @@ func SetVal(vals []Value) Value {
|
||||||
return Value{
|
return Value{
|
||||||
ty: Set(elementType),
|
ty: Set(elementType),
|
||||||
v: rawVal,
|
v: rawVal,
|
||||||
}
|
}.WithMarks(markSets...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetValFromValueSet returns a Value of set type based on an already-constructed
|
// 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
|
// GoString is an implementation of fmt.GoStringer that produces concise
|
||||||
// source-like representations of values suitable for use in debug messages.
|
// source-like representations of values suitable for use in debug messages.
|
||||||
func (val Value) GoString() string {
|
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 {
|
if val == NilVal {
|
||||||
return "cty.NilVal"
|
return "cty.NilVal"
|
||||||
}
|
}
|
||||||
|
@ -82,7 +94,11 @@ func (val Value) GoString() string {
|
||||||
vals := val.AsValueMap()
|
vals := val.AsValueMap()
|
||||||
return fmt.Sprintf("cty.ObjectVal(%#v)", vals)
|
return fmt.Sprintf("cty.ObjectVal(%#v)", vals)
|
||||||
case val.ty.IsCapsuleType():
|
case val.ty.IsCapsuleType():
|
||||||
return fmt.Sprintf("cty.CapsuleVal(%#v, %#v)", val.ty, val.v)
|
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
|
// Default exposes implementation details, so should actually cover
|
||||||
|
@ -101,6 +117,12 @@ func (val Value) GoString() string {
|
||||||
// Use RawEquals to compare if two values are equal *ignoring* the
|
// Use RawEquals to compare if two values are equal *ignoring* the
|
||||||
// short-circuit rules and the exception for null values.
|
// short-circuit rules and the exception for null values.
|
||||||
func (val Value) Equals(other Value) Value {
|
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.
|
// Start by handling Unknown values before considering types.
|
||||||
// This needs to be done since Null values are always equal regardless of
|
// This needs to be done since Null values are always equal regardless of
|
||||||
// type.
|
// type.
|
||||||
|
@ -288,10 +310,22 @@ func (val Value) Equals(other Value) Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ty.IsCapsuleType():
|
case ty.IsCapsuleType():
|
||||||
// A capsule type's encapsulated value is a pointer to a value of its
|
impl := val.ty.CapsuleOps().Equals
|
||||||
// native type, so we can just compare these to get the identity test
|
if impl == nil {
|
||||||
// we need.
|
impl := val.ty.CapsuleOps().RawEquals
|
||||||
return BoolVal(val.v == other.v)
|
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:
|
default:
|
||||||
// should never happen
|
// should never happen
|
||||||
|
@ -314,6 +348,7 @@ func (val Value) NotEqual(other Value) Value {
|
||||||
// or null values. For more robust handling with unknown value
|
// or null values. For more robust handling with unknown value
|
||||||
// short-circuiting, use val.Equals(cty.True).
|
// short-circuiting, use val.Equals(cty.True).
|
||||||
func (val Value) True() bool {
|
func (val Value) True() bool {
|
||||||
|
val.assertUnmarked()
|
||||||
if val.ty != Bool {
|
if val.ty != Bool {
|
||||||
panic("not bool")
|
panic("not bool")
|
||||||
}
|
}
|
||||||
|
@ -338,6 +373,13 @@ func (val Value) RawEquals(other Value) bool {
|
||||||
if !val.ty.Equals(other.ty) {
|
if !val.ty.Equals(other.ty) {
|
||||||
return false
|
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()) {
|
if (!val.IsKnown()) && (!other.IsKnown()) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -448,10 +490,14 @@ func (val Value) RawEquals(other Value) bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
case ty.IsCapsuleType():
|
case ty.IsCapsuleType():
|
||||||
// A capsule type's encapsulated value is a pointer to a value of its
|
impl := val.ty.CapsuleOps().RawEquals
|
||||||
// native type, so we can just compare these to get the identity test
|
if impl == nil {
|
||||||
// we need.
|
// A capsule type's encapsulated value is a pointer to a value of its
|
||||||
return val.v == other.v
|
// 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:
|
default:
|
||||||
// should never happen
|
// 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
|
// Add returns the sum of the receiver and the given other value. Both values
|
||||||
// must be numbers; this method will panic if not.
|
// must be numbers; this method will panic if not.
|
||||||
func (val Value) Add(other Value) Value {
|
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 {
|
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
|
||||||
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
||||||
return *shortCircuit
|
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
|
// Subtract returns receiver minus the given other value. Both values must be
|
||||||
// numbers; this method will panic if not.
|
// numbers; this method will panic if not.
|
||||||
func (val Value) Subtract(other Value) Value {
|
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 {
|
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
|
||||||
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
||||||
return *shortCircuit
|
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.
|
// 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.
|
// This method will panic when given a value of any other type.
|
||||||
func (val Value) Negate() Value {
|
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 {
|
if shortCircuit := mustTypeCheck(Number, Number, val); shortCircuit != nil {
|
||||||
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
||||||
return *shortCircuit
|
return *shortCircuit
|
||||||
|
@ -498,6 +561,12 @@ func (val Value) Negate() Value {
|
||||||
// Multiply returns the product of the receiver and the given other value.
|
// Multiply returns the product of the receiver and the given other value.
|
||||||
// Both values must be numbers; this method will panic if not.
|
// Both values must be numbers; this method will panic if not.
|
||||||
func (val Value) Multiply(other Value) Value {
|
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 {
|
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
|
||||||
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
||||||
return *shortCircuit
|
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
|
// If both values are zero or infinity, this function will panic with
|
||||||
// an instance of big.ErrNaN.
|
// an instance of big.ErrNaN.
|
||||||
func (val Value) Divide(other Value) Value {
|
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 {
|
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
|
||||||
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
||||||
return *shortCircuit
|
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
|
// may wish to disallow such things outright or implement their own modulo
|
||||||
// if they disagree with the interpretation used here.
|
// if they disagree with the interpretation used here.
|
||||||
func (val Value) Modulo(other Value) Value {
|
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 {
|
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
|
||||||
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
||||||
return *shortCircuit
|
return *shortCircuit
|
||||||
|
@ -576,6 +657,11 @@ func (val Value) Modulo(other Value) Value {
|
||||||
// Absolute returns the absolute (signless) value of the receiver, which must
|
// Absolute returns the absolute (signless) value of the receiver, which must
|
||||||
// be a number or this method will panic.
|
// be a number or this method will panic.
|
||||||
func (val Value) Absolute() Value {
|
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 {
|
if shortCircuit := mustTypeCheck(Number, Number, val); shortCircuit != nil {
|
||||||
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
shortCircuit = forceShortCircuitType(shortCircuit, Number)
|
||||||
return *shortCircuit
|
return *shortCircuit
|
||||||
|
@ -596,6 +682,11 @@ func (val Value) Absolute() Value {
|
||||||
// This method may be called on a value whose type is DynamicPseudoType,
|
// This method may be called on a value whose type is DynamicPseudoType,
|
||||||
// in which case the result will also be DynamicVal.
|
// in which case the result will also be DynamicVal.
|
||||||
func (val Value) GetAttr(name string) Value {
|
func (val Value) GetAttr(name string) Value {
|
||||||
|
if val.IsMarked() {
|
||||||
|
val, valMarks := val.Unmark()
|
||||||
|
return val.GetAttr(name).WithMarks(valMarks)
|
||||||
|
}
|
||||||
|
|
||||||
if val.ty == DynamicPseudoType {
|
if val.ty == DynamicPseudoType {
|
||||||
return DynamicVal
|
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,
|
// This method may be called on a value whose type is DynamicPseudoType,
|
||||||
// in which case the result will also be the DynamicValue.
|
// in which case the result will also be the DynamicValue.
|
||||||
func (val Value) Index(key Value) Value {
|
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 {
|
if val.ty == DynamicPseudoType {
|
||||||
return DynamicVal
|
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
|
// This method will panic if the receiver is not indexable, but does not
|
||||||
// impose any panic-causing type constraints on the key.
|
// impose any panic-causing type constraints on the key.
|
||||||
func (val Value) HasIndex(key Value) Value {
|
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 {
|
if val.ty == DynamicPseudoType {
|
||||||
return UnknownVal(Bool)
|
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.
|
// 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 {
|
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()
|
ty := val.Type()
|
||||||
|
|
||||||
if !ty.IsSetType() {
|
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
|
// of a string, call AsString and take the length of the native Go string
|
||||||
// that is returned.
|
// that is returned.
|
||||||
func (val Value) Length() Value {
|
func (val Value) Length() Value {
|
||||||
|
if val.IsMarked() {
|
||||||
|
val, valMarks := val.Unmark()
|
||||||
|
return val.Length().WithMarks(valMarks)
|
||||||
|
}
|
||||||
|
|
||||||
if val.Type().IsTupleType() {
|
if val.Type().IsTupleType() {
|
||||||
// For tuples, we can return the length even if the value is not known.
|
// For tuples, we can return the length even if the value is not known.
|
||||||
return NumberIntVal(int64(val.Type().Length()))
|
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
|
// This is an integration method provided for the convenience of code bridging
|
||||||
// into Go's type system.
|
// into Go's type system.
|
||||||
func (val Value) LengthInt() int {
|
func (val Value) LengthInt() int {
|
||||||
|
val.assertUnmarked()
|
||||||
if val.Type().IsTupleType() {
|
if val.Type().IsTupleType() {
|
||||||
// For tuples, we can return the length even if the value is not known.
|
// For tuples, we can return the length even if the value is not known.
|
||||||
return val.Type().Length()
|
return val.Type().Length()
|
||||||
|
@ -915,6 +1030,7 @@ func (val Value) LengthInt() int {
|
||||||
// ElementIterator is an integration method, so it cannot handle Unknown
|
// ElementIterator is an integration method, so it cannot handle Unknown
|
||||||
// values. This method will panic if the receiver is Unknown.
|
// values. This method will panic if the receiver is Unknown.
|
||||||
func (val Value) ElementIterator() ElementIterator {
|
func (val Value) ElementIterator() ElementIterator {
|
||||||
|
val.assertUnmarked()
|
||||||
if !val.IsKnown() {
|
if !val.IsKnown() {
|
||||||
panic("can't use ElementIterator on unknown value")
|
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
|
// ForEachElement is an integration method, so it cannot handle Unknown
|
||||||
// values. This method will panic if the receiver is Unknown.
|
// values. This method will panic if the receiver is Unknown.
|
||||||
func (val Value) ForEachElement(cb ElementCallback) bool {
|
func (val Value) ForEachElement(cb ElementCallback) bool {
|
||||||
|
val.assertUnmarked()
|
||||||
it := val.ElementIterator()
|
it := val.ElementIterator()
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
key, val := it.Element()
|
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
|
// Not returns the logical inverse of the receiver, which must be of type
|
||||||
// Bool or this method will panic.
|
// Bool or this method will panic.
|
||||||
func (val Value) Not() Value {
|
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 {
|
if shortCircuit := mustTypeCheck(Bool, Bool, val); shortCircuit != nil {
|
||||||
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
||||||
return *shortCircuit
|
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
|
// 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.
|
// value, which must both be of type Bool or this method will panic.
|
||||||
func (val Value) And(other Value) Value {
|
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 {
|
if shortCircuit := mustTypeCheck(Bool, Bool, val, other); shortCircuit != nil {
|
||||||
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
||||||
return *shortCircuit
|
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
|
// 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.
|
// value, which must both be of type Bool or this method will panic.
|
||||||
func (val Value) Or(other Value) Value {
|
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 {
|
if shortCircuit := mustTypeCheck(Bool, Bool, val, other); shortCircuit != nil {
|
||||||
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
||||||
return *shortCircuit
|
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,
|
// LessThan returns True if the receiver is less than the other given value,
|
||||||
// which must both be numbers or this method will panic.
|
// which must both be numbers or this method will panic.
|
||||||
func (val Value) LessThan(other Value) Value {
|
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 {
|
if shortCircuit := mustTypeCheck(Number, Bool, val, other); shortCircuit != nil {
|
||||||
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
||||||
return *shortCircuit
|
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
|
// GreaterThan returns True if the receiver is greater than the other given
|
||||||
// value, which must both be numbers or this method will panic.
|
// value, which must both be numbers or this method will panic.
|
||||||
func (val Value) GreaterThan(other Value) Value {
|
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 {
|
if shortCircuit := mustTypeCheck(Number, Bool, val, other); shortCircuit != nil {
|
||||||
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
shortCircuit = forceShortCircuitType(shortCircuit, Bool)
|
||||||
return *shortCircuit
|
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
|
// AsString returns the native string from a non-null, non-unknown cty.String
|
||||||
// value, or panics if called on any other value.
|
// value, or panics if called on any other value.
|
||||||
func (val Value) AsString() string {
|
func (val Value) AsString() string {
|
||||||
|
val.assertUnmarked()
|
||||||
if val.ty != String {
|
if val.ty != String {
|
||||||
panic("not a 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
|
// For more convenient conversions to other native numeric types, use the
|
||||||
// "gocty" package.
|
// "gocty" package.
|
||||||
func (val Value) AsBigFloat() *big.Float {
|
func (val Value) AsBigFloat() *big.Float {
|
||||||
|
val.assertUnmarked()
|
||||||
if val.ty != Number {
|
if val.ty != Number {
|
||||||
panic("not a 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
|
// For more convenient conversions to slices of more specific types, use
|
||||||
// the "gocty" package.
|
// the "gocty" package.
|
||||||
func (val Value) AsValueSlice() []Value {
|
func (val Value) AsValueSlice() []Value {
|
||||||
|
val.assertUnmarked()
|
||||||
l := val.LengthInt()
|
l := val.LengthInt()
|
||||||
if l == 0 {
|
if l == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
@ -1084,6 +1233,7 @@ func (val Value) AsValueSlice() []Value {
|
||||||
// For more convenient conversions to maps of more specific types, use
|
// For more convenient conversions to maps of more specific types, use
|
||||||
// the "gocty" package.
|
// the "gocty" package.
|
||||||
func (val Value) AsValueMap() map[string]Value {
|
func (val Value) AsValueMap() map[string]Value {
|
||||||
|
val.assertUnmarked()
|
||||||
l := val.LengthInt()
|
l := val.LengthInt()
|
||||||
if l == 0 {
|
if l == 0 {
|
||||||
return nil
|
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.
|
// The returned ValueSet can store only values of the receiver's element type.
|
||||||
func (val Value) AsValueSet() ValueSet {
|
func (val Value) AsValueSet() ValueSet {
|
||||||
|
val.assertUnmarked()
|
||||||
if !val.Type().IsCollectionType() {
|
if !val.Type().IsCollectionType() {
|
||||||
panic("not a collection type")
|
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
|
// the value. Since cty considers values to be immutable, it is strongly
|
||||||
// recommended to treat the encapsulated value itself as immutable too.
|
// recommended to treat the encapsulated value itself as immutable too.
|
||||||
func (val Value) EncapsulatedValue() interface{} {
|
func (val Value) EncapsulatedValue() interface{} {
|
||||||
|
val.assertUnmarked()
|
||||||
if !val.Type().IsCapsuleType() {
|
if !val.Type().IsCapsuleType() {
|
||||||
panic("not a capsule-typed value")
|
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/hcl/strconv
|
||||||
github.com/hashicorp/hcl/json/scanner
|
github.com/hashicorp/hcl/json/scanner
|
||||||
github.com/hashicorp/hcl/json/token
|
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
|
||||||
github.com/hashicorp/hcl/v2/hclsyntax
|
github.com/hashicorp/hcl/v2/hclsyntax
|
||||||
github.com/hashicorp/hcl/v2/hcldec
|
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/gohcl
|
||||||
github.com/hashicorp/hcl/v2/ext/typeexpr
|
github.com/hashicorp/hcl/v2/ext/typeexpr
|
||||||
github.com/hashicorp/hcl/v2/ext/dynblock
|
github.com/hashicorp/hcl/v2/ext/dynblock
|
||||||
|
github.com/hashicorp/hcl/v2/ext/customdecode
|
||||||
github.com/hashicorp/hcl/v2/hcltest
|
github.com/hashicorp/hcl/v2/hcltest
|
||||||
# github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590
|
# github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590
|
||||||
github.com/hashicorp/hil
|
github.com/hashicorp/hil
|
||||||
|
@ -484,7 +485,7 @@ github.com/vmihailenco/msgpack/codes
|
||||||
github.com/xanzy/ssh-agent
|
github.com/xanzy/ssh-agent
|
||||||
# github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557
|
# github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557
|
||||||
github.com/xlab/treeprint
|
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
|
||||||
github.com/zclconf/go-cty/cty/gocty
|
github.com/zclconf/go-cty/cty/gocty
|
||||||
github.com/zclconf/go-cty/cty/convert
|
github.com/zclconf/go-cty/cty/convert
|
||||||
|
|
Loading…
Reference in New Issue