Merge pull request #3318 from crazy-max/bump-hcl-deps
vendor: update hcl dependencies
This commit is contained in:
@@ -24,9 +24,9 @@ require (
|
||||
github.com/google/go-dap v0.12.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hashicorp/go-cty-funcs v0.0.0-20250210171435-dda779884a9f
|
||||
github.com/hashicorp/go-cty-funcs v0.0.0-20250818135842-6aab67130928
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/hcl/v2 v2.23.0
|
||||
github.com/hashicorp/hcl/v2 v2.24.0
|
||||
github.com/in-toto/in-toto-golang v0.9.0
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/moby/buildkit v0.24.0
|
||||
@@ -48,7 +48,7 @@ require (
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0
|
||||
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250408171107-3dd17559e117
|
||||
github.com/zclconf/go-cty v1.16.2
|
||||
github.com/zclconf/go-cty v1.17.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0
|
||||
go.opentelemetry.io/otel v1.35.0
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0
|
||||
@@ -59,8 +59,8 @@ require (
|
||||
golang.org/x/mod v0.24.0
|
||||
golang.org/x/sync v0.16.0
|
||||
golang.org/x/sys v0.33.0
|
||||
golang.org/x/term v0.31.0
|
||||
golang.org/x/text v0.24.0
|
||||
golang.org/x/term v0.32.0
|
||||
golang.org/x/text v0.25.0
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a
|
||||
google.golang.org/grpc v1.72.2
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1
|
||||
@@ -122,7 +122,7 @@ require (
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
@@ -158,7 +158,7 @@ require (
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/crypto v0.38.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/oauth2 v0.29.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
|
||||
@@ -203,12 +203,12 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-cty-funcs v0.0.0-20250210171435-dda779884a9f h1:c+rwTuPxlEHiskGDCl53snuw344sDO/4KU5KSoWhieI=
|
||||
github.com/hashicorp/go-cty-funcs v0.0.0-20250210171435-dda779884a9f/go.mod h1:0AzBnolcBFa/tA5iF378GgeeiZ955EdVdC4TuxJ66m4=
|
||||
github.com/hashicorp/go-cty-funcs v0.0.0-20250818135842-6aab67130928 h1:NFpfJOqEV8DUhomtXx9A0FfcMg7OCYRugdyw6Ar/Y7s=
|
||||
github.com/hashicorp/go-cty-funcs v0.0.0-20250818135842-6aab67130928/go.mod h1:YC9ASYt9Z9sQEAtzCe+yaAzi3E7wcxfRphDXtwZoWC0=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
|
||||
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
|
||||
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
|
||||
github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU=
|
||||
github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo=
|
||||
@@ -250,8 +250,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
|
||||
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
|
||||
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
@@ -403,8 +403,8 @@ github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70=
|
||||
github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
||||
github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0=
|
||||
github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U=
|
||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
|
||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
@@ -451,8 +451,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
@@ -485,12 +485,12 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
1.23
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
version: "2"
|
||||
issues:
|
||||
max-issues-per-linter: 0 # show all issues found by each linter
|
||||
max-same-issues: 0 # don't ignore same issues
|
||||
linters:
|
||||
exclusions:
|
||||
rules:
|
||||
- path: hclsyntax/scan_string_lit.go # generated file, ignore errors
|
||||
linters:
|
||||
- unused
|
||||
- staticcheck
|
||||
- path: hclsyntax/scan_tokens.go # generated file, ignore errors
|
||||
linters:
|
||||
- unused
|
||||
- staticcheck
|
||||
+18
@@ -1,5 +1,23 @@
|
||||
# HCL Changelog
|
||||
|
||||
## v2.24.0 (July 7, 2025)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Add support for decoding block and attribute source ranges when using `gohcl`. ([#703](https://github.com/hashicorp/hcl/pull/703))
|
||||
* hclsyntax: Detect and reject invalid nested splat result. ([#724](https://github.com/hashicorp/hcl/pull/724))
|
||||
|
||||
### Bugs Fixed
|
||||
|
||||
* Correct handling of unknown objects in Index function. ([#763](https://github.com/hashicorp/hcl/pull/763))
|
||||
|
||||
## v2.23.0 (November 15, 2024)
|
||||
|
||||
### Bugs Fixed
|
||||
|
||||
* Preserve marks when traversing through unknown values. ([#699](https://github.com/hashicorp/hcl/pull/699))
|
||||
* Retain marks through conditional and for expressions. ([#710](https://github.com/hashicorp/hcl/pull/710))
|
||||
|
||||
## v2.22.0 (August 26, 2024)
|
||||
|
||||
### Enhancements
|
||||
|
||||
+4
-4
@@ -106,10 +106,10 @@ func (d *Diagnostic) Error() string {
|
||||
// APIs that normally deal in vanilla Go errors.
|
||||
func (d Diagnostics) Error() string {
|
||||
count := len(d)
|
||||
switch {
|
||||
case count == 0:
|
||||
switch count {
|
||||
case 0:
|
||||
return "no diagnostics"
|
||||
case count == 1:
|
||||
case 1:
|
||||
return d[0].Error()
|
||||
default:
|
||||
return fmt.Sprintf("%s, and %d other diagnostic(s)", d[0].Error(), count-1)
|
||||
@@ -121,7 +121,7 @@ func (d Diagnostics) Error() string {
|
||||
// This is provided as a convenience for returning from a function that
|
||||
// collects and then returns a set of diagnostics:
|
||||
//
|
||||
// return nil, diags.Append(&hcl.Diagnostic{ ... })
|
||||
// return nil, diags.Append(&hcl.Diagnostic{ ... })
|
||||
//
|
||||
// Note that this modifies the array underlying the diagnostics slice, so
|
||||
// must be used carefully within a single codepath. It is incorrect (and rude)
|
||||
|
||||
+43
-12
@@ -71,7 +71,10 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
|
||||
severityStr = "???????"
|
||||
}
|
||||
|
||||
fmt.Fprintf(w.wr, "%s%s%s: %s\n\n", colorCode, severityStr, resetCode, diag.Summary)
|
||||
_, err := fmt.Fprintf(w.wr, "%s%s%s: %s\n\n", colorCode, severityStr, resetCode, diag.Summary)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write failed: %w", err)
|
||||
}
|
||||
|
||||
if diag.Subject != nil {
|
||||
snipRange := *diag.Subject
|
||||
@@ -97,7 +100,10 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
|
||||
|
||||
file := w.files[diag.Subject.Filename]
|
||||
if file == nil || file.Bytes == nil {
|
||||
fmt.Fprintf(w.wr, " on %s line %d:\n (source code not available)\n\n", diag.Subject.Filename, diag.Subject.Start.Line)
|
||||
_, err = fmt.Fprintf(w.wr, " on %s line %d:\n (source code not available)\n\n", diag.Subject.Filename, diag.Subject.Start.Line)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
|
||||
var contextLine string
|
||||
@@ -108,7 +114,10 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(w.wr, " on %s line %d%s:\n", diag.Subject.Filename, diag.Subject.Start.Line, contextLine)
|
||||
_, err = fmt.Fprintf(w.wr, " on %s line %d%s:\n", diag.Subject.Filename, diag.Subject.Start.Line, contextLine)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write failed: %w", err)
|
||||
}
|
||||
|
||||
src := file.Bytes
|
||||
sc := NewRangeScanner(src, diag.Subject.Filename, bufio.ScanLines)
|
||||
@@ -121,23 +130,32 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
|
||||
|
||||
beforeRange, highlightedRange, afterRange := lineRange.PartitionAround(highlightRange)
|
||||
if highlightedRange.Empty() {
|
||||
fmt.Fprintf(w.wr, "%4d: %s\n", lineRange.Start.Line, sc.Bytes())
|
||||
_, err = fmt.Fprintf(w.wr, "%4d: %s\n", lineRange.Start.Line, sc.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("write failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
before := beforeRange.SliceBytes(src)
|
||||
highlighted := highlightedRange.SliceBytes(src)
|
||||
after := afterRange.SliceBytes(src)
|
||||
fmt.Fprintf(
|
||||
_, err = fmt.Fprintf(
|
||||
w.wr, "%4d: %s%s%s%s%s\n",
|
||||
lineRange.Start.Line,
|
||||
before,
|
||||
highlightCode, highlighted, resetCode,
|
||||
after,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
w.wr.Write([]byte{'\n'})
|
||||
_, err = w.wr.Write([]byte{'\n'})
|
||||
if err != nil {
|
||||
return fmt.Errorf("write failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if diag.Expression != nil && diag.EvalContext != nil {
|
||||
@@ -182,16 +200,26 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
|
||||
for i, stmt := range stmts {
|
||||
switch i {
|
||||
case 0:
|
||||
w.wr.Write([]byte{'w', 'i', 't', 'h', ' '})
|
||||
_, err = w.wr.Write([]byte{'w', 'i', 't', 'h', ' '})
|
||||
default:
|
||||
w.wr.Write([]byte{' ', ' ', ' ', ' ', ' '})
|
||||
_, err = w.wr.Write([]byte{' ', ' ', ' ', ' ', ' '})
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("write failed: %w", err)
|
||||
}
|
||||
|
||||
_, err = w.wr.Write([]byte(stmt))
|
||||
if err != nil {
|
||||
return fmt.Errorf("write failed: %w", err)
|
||||
}
|
||||
w.wr.Write([]byte(stmt))
|
||||
switch i {
|
||||
case last:
|
||||
w.wr.Write([]byte{'.', '\n', '\n'})
|
||||
_, err = w.wr.Write([]byte{'.', '\n', '\n'})
|
||||
default:
|
||||
w.wr.Write([]byte{',', '\n'})
|
||||
_, err = w.wr.Write([]byte{',', '\n'})
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("write failed: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,7 +230,10 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
|
||||
if w.width != 0 {
|
||||
detail = wordwrap.WrapString(detail, w.width)
|
||||
}
|
||||
fmt.Fprintf(w.wr, "%s\n\n", detail)
|
||||
_, err = fmt.Fprintf(w.wr, "%s\n\n", detail)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+16
-16
@@ -9,25 +9,25 @@
|
||||
// configurations in either native HCL syntax or JSON syntax into a Go struct
|
||||
// type:
|
||||
//
|
||||
// package main
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "log"
|
||||
// "github.com/hashicorp/hcl/v2/hclsimple"
|
||||
// )
|
||||
// import (
|
||||
// "log"
|
||||
// "github.com/hashicorp/hcl/v2/hclsimple"
|
||||
// )
|
||||
//
|
||||
// type Config struct {
|
||||
// LogLevel string `hcl:"log_level"`
|
||||
// }
|
||||
// type Config struct {
|
||||
// LogLevel string `hcl:"log_level"`
|
||||
// }
|
||||
//
|
||||
// func main() {
|
||||
// var config Config
|
||||
// err := hclsimple.DecodeFile("config.hcl", nil, &config)
|
||||
// if err != nil {
|
||||
// log.Fatalf("Failed to load configuration: %s", err)
|
||||
// }
|
||||
// log.Printf("Configuration is %#v", config)
|
||||
// }
|
||||
// func main() {
|
||||
// var config Config
|
||||
// err := hclsimple.DecodeFile("config.hcl", nil, &config)
|
||||
// if err != nil {
|
||||
// log.Fatalf("Failed to load configuration: %s", err)
|
||||
// }
|
||||
// log.Printf("Configuration is %#v", config)
|
||||
// }
|
||||
//
|
||||
// If your application needs more control over the evaluation of the
|
||||
// configuration, you can use the functions in the subdirectories hclparse,
|
||||
|
||||
+3
-3
@@ -8,7 +8,7 @@
|
||||
// use any of the usual language operators. This is similar to type expressions
|
||||
// in statically-typed programming languages.
|
||||
//
|
||||
// variable "example" {
|
||||
// type = list(string)
|
||||
// }
|
||||
// variable "example" {
|
||||
// type = list(string)
|
||||
// }
|
||||
package typeexpr
|
||||
|
||||
+2
-1
@@ -46,7 +46,8 @@ func TypeConstraintFromVal(v cty.Value) cty.Type {
|
||||
// ConvertFunc is a cty function that implements type conversions.
|
||||
//
|
||||
// Its signature is as follows:
|
||||
// convert(value, type_constraint)
|
||||
//
|
||||
// convert(value, type_constraint)
|
||||
//
|
||||
// ...where type_constraint is a type constraint expression as defined by
|
||||
// typeexpr.TypeConstraint.
|
||||
|
||||
+33
-8
@@ -9,9 +9,10 @@ import (
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// DecodeBody extracts the configuration within the given body into the given
|
||||
@@ -110,7 +111,7 @@ func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value)
|
||||
}
|
||||
|
||||
// As a special case, if the target is of type hcl.Expression then
|
||||
// we'll assign an actual expression that evalues to a cty null,
|
||||
// we'll assign an actual expression that evaluates to a cty null,
|
||||
// so the caller can deal with it within the cty realm rather
|
||||
// than within the Go realm.
|
||||
synthExpr := hcl.StaticExpr(cty.NullVal(cty.DynamicPseudoType), body.MissingItemRange())
|
||||
@@ -118,6 +119,18 @@ func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value)
|
||||
continue
|
||||
}
|
||||
|
||||
if attrRange, exists := tags.AttributeRange[name]; exists {
|
||||
val.Field(attrRange).Set(reflect.ValueOf(attr.Range))
|
||||
}
|
||||
|
||||
if attrNameRange, exists := tags.AttributeNameRange[name]; exists {
|
||||
val.Field(attrNameRange).Set(reflect.ValueOf(attr.NameRange))
|
||||
}
|
||||
|
||||
if attrValueRange, exists := tags.AttributeValueRange[name]; exists {
|
||||
val.Field(attrValueRange).Set(reflect.ValueOf(attr.Expr.Range()))
|
||||
}
|
||||
|
||||
switch {
|
||||
case attrType.AssignableTo(field.Type):
|
||||
fieldV.Set(reflect.ValueOf(attr))
|
||||
@@ -182,7 +195,7 @@ func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value)
|
||||
case isSlice:
|
||||
elemType := ty
|
||||
if isPtr {
|
||||
elemType = reflect.PtrTo(ty)
|
||||
elemType = reflect.PointerTo(ty)
|
||||
}
|
||||
sli := val.Field(fieldIdx)
|
||||
if sli.IsNil() {
|
||||
@@ -263,14 +276,26 @@ func decodeBodyToMap(body hcl.Body, ctx *hcl.EvalContext, v reflect.Value) hcl.D
|
||||
func decodeBlockToValue(block *hcl.Block, ctx *hcl.EvalContext, v reflect.Value) hcl.Diagnostics {
|
||||
diags := decodeBodyToValue(block.Body, ctx, v)
|
||||
|
||||
if len(block.Labels) > 0 {
|
||||
blockTags := getFieldTags(v.Type())
|
||||
for li, lv := range block.Labels {
|
||||
lfieldIdx := blockTags.Labels[li].FieldIndex
|
||||
v.Field(lfieldIdx).Set(reflect.ValueOf(lv))
|
||||
blockTags := getFieldTags(v.Type())
|
||||
for li, lv := range block.Labels {
|
||||
lfieldIdx := blockTags.Labels[li].FieldIndex
|
||||
lfieldName := blockTags.Labels[li].Name
|
||||
|
||||
v.Field(lfieldIdx).Set(reflect.ValueOf(lv))
|
||||
|
||||
if ix, exists := blockTags.LabelRange[lfieldName]; exists {
|
||||
v.Field(ix).Set(reflect.ValueOf(block.LabelRanges[li]))
|
||||
}
|
||||
}
|
||||
|
||||
if blockTags.TypeRange != nil {
|
||||
v.Field(*blockTags.TypeRange).Set(reflect.ValueOf(block.TypeRange))
|
||||
}
|
||||
|
||||
if blockTags.DefRange != nil {
|
||||
v.Field(*blockTags.DefRange).Set(reflect.ValueOf(block.DefRange))
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
|
||||
+38
-8
@@ -10,18 +10,18 @@
|
||||
// A struct field tag scheme is used, similar to other decoding and
|
||||
// unmarshalling libraries. The tags are formatted as in the following example:
|
||||
//
|
||||
// ThingType string `hcl:"thing_type,attr"`
|
||||
// ThingType string `hcl:"thing_type,attr"`
|
||||
//
|
||||
// Within each tag there are two comma-separated tokens. The first is the
|
||||
// name of the corresponding construct in configuration, while the second
|
||||
// is a keyword giving the kind of construct expected. The following
|
||||
// kind keywords are supported:
|
||||
//
|
||||
// attr (the default) indicates that the value is to be populated from an attribute
|
||||
// block indicates that the value is to populated from a block
|
||||
// label indicates that the value is to populated from a block label
|
||||
// optional is the same as attr, but the field is optional
|
||||
// remain indicates that the value is to be populated from the remaining body after populating other fields
|
||||
// attr (the default) indicates that the value is to be populated from an attribute
|
||||
// block indicates that the value is to populated from a block
|
||||
// label indicates that the value is to populated from a block label
|
||||
// optional is the same as attr, but the field is optional
|
||||
// remain indicates that the value is to be populated from the remaining body after populating other fields
|
||||
//
|
||||
// "attr" fields may either be of type *hcl.Expression, in which case the raw
|
||||
// expression is assigned, or of any type accepted by gocty, in which case
|
||||
@@ -40,8 +40,9 @@
|
||||
//
|
||||
// "label" fields are considered only in a struct used as the type of a field
|
||||
// marked as "block", and are used sequentially to capture the labels of
|
||||
// the blocks being decoded. In this case, the name token is used only as
|
||||
// an identifier for the label in diagnostic messages.
|
||||
// the blocks being decoded. In this case, the name token is used (a) as
|
||||
// an identifier for the label in diagnostic messages and (b) to match the
|
||||
// which with the equivalent "label_range" field (if it exists).
|
||||
//
|
||||
// "optional" fields behave like "attr" fields, but they are optional
|
||||
// and will not give parsing errors if they are missing.
|
||||
@@ -52,6 +53,35 @@
|
||||
// present then any attributes or blocks not matched by another valid tag
|
||||
// will cause an error diagnostic.
|
||||
//
|
||||
// "def_range" can be placed on a single field that must be of type hcl.Range.
|
||||
// This field is only considered in a struct used as the type of a field marked
|
||||
// as "block", and is used to capture the range of the block's definition.
|
||||
//
|
||||
// "type_range" can be placed on a single field that must be of type hcl.Range.
|
||||
// This field is only considered in a struct used as the type of a field marked
|
||||
// as "block", and is used to capture the range of the block's type label.
|
||||
//
|
||||
// "label_range" can be placed on multiple fields that must be of type
|
||||
// hcl.Range. This field is only considered in a struct used as the type of a
|
||||
// field marked as "block", and is used to capture the range of the block's
|
||||
// labels. The name token is used to match with the equivalent "label" field
|
||||
// that this range will specify.
|
||||
//
|
||||
// "attr_range" can be placed on multiple fields that must be of type hcl.Range.
|
||||
// This field will be assigned the complete hcl.Range for the attribute with
|
||||
// the corresponding name. The name token is used to match with the name of the
|
||||
// attribute that this range will specify.
|
||||
//
|
||||
// "attr_name_range" can be placed on multiple fields that must be of type
|
||||
// hcl.Range. This field will be assigned the hcl.Range for the name of the
|
||||
// attribute with the corresponding name. The name token is used to match with
|
||||
// the name of the attribute that this range will specify.
|
||||
//
|
||||
// "attr_value_range" can be placed on multiple fields that must be of type
|
||||
// hcl.Range. This field will be assigned the hcl.Range for the value of the
|
||||
// attribute with the corresponding name. The name token is used to match with
|
||||
// the name of the attribute that this range will specify.
|
||||
//
|
||||
// Only a subset of this tagging/typing vocabulary is supported for the
|
||||
// "Encode" family of functions. See the EncodeIntoBody docs for full details
|
||||
// on the constraints there.
|
||||
|
||||
+36
-3
@@ -118,18 +118,31 @@ type fieldTags struct {
|
||||
Remain *int
|
||||
Body *int
|
||||
Optional map[string]bool
|
||||
|
||||
AttributeRange map[string]int
|
||||
AttributeNameRange map[string]int
|
||||
AttributeValueRange map[string]int
|
||||
|
||||
DefRange *int
|
||||
TypeRange *int
|
||||
LabelRange map[string]int
|
||||
}
|
||||
|
||||
type labelField struct {
|
||||
FieldIndex int
|
||||
RangeIndex int
|
||||
Name string
|
||||
}
|
||||
|
||||
func getFieldTags(ty reflect.Type) *fieldTags {
|
||||
ret := &fieldTags{
|
||||
Attributes: map[string]int{},
|
||||
Blocks: map[string]int{},
|
||||
Optional: map[string]bool{},
|
||||
Attributes: map[string]int{},
|
||||
Blocks: map[string]int{},
|
||||
Optional: map[string]bool{},
|
||||
AttributeRange: map[string]int{},
|
||||
AttributeNameRange: map[string]int{},
|
||||
AttributeValueRange: map[string]int{},
|
||||
LabelRange: map[string]int{},
|
||||
}
|
||||
|
||||
ct := ty.NumField()
|
||||
@@ -175,6 +188,26 @@ func getFieldTags(ty reflect.Type) *fieldTags {
|
||||
case "optional":
|
||||
ret.Attributes[name] = i
|
||||
ret.Optional[name] = true
|
||||
case "def_range":
|
||||
if ret.DefRange != nil {
|
||||
panic("only one 'def_range' tag is permitted")
|
||||
}
|
||||
idx := i // copy, because this loop will continue assigning to i
|
||||
ret.DefRange = &idx
|
||||
case "type_range":
|
||||
if ret.TypeRange != nil {
|
||||
panic("only one 'type_range' tag is permitted")
|
||||
}
|
||||
idx := i // copy, because this loop will continue assigning to i
|
||||
ret.TypeRange = &idx
|
||||
case "label_range":
|
||||
ret.LabelRange[name] = i
|
||||
case "attr_range":
|
||||
ret.AttributeRange[name] = i
|
||||
case "attr_name_range":
|
||||
ret.AttributeNameRange[name] = i
|
||||
case "attr_value_range":
|
||||
ret.AttributeValueRange[name] = i
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid hcl field tag kind %q on %s %q", kind, field.Type.String(), field.Name))
|
||||
}
|
||||
|
||||
+1
-1
@@ -14,6 +14,6 @@ var victimBody hcl.Body
|
||||
|
||||
var exprType = reflect.TypeOf(&victimExpr).Elem()
|
||||
var bodyType = reflect.TypeOf(&victimBody).Elem()
|
||||
var blockType = reflect.TypeOf((*hcl.Block)(nil))
|
||||
|
||||
var attrType = reflect.TypeOf((*hcl.Attribute)(nil))
|
||||
var attrsType = reflect.TypeOf(hcl.Attributes(nil))
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ package hclparse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
@@ -70,7 +70,7 @@ func (p *Parser) ParseHCLFile(filename string) (*hcl.File, hcl.Diagnostics) {
|
||||
return existing, nil
|
||||
}
|
||||
|
||||
src, err := ioutil.ReadFile(filename)
|
||||
src, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, hcl.Diagnostics{
|
||||
{
|
||||
|
||||
+17
@@ -1796,6 +1796,7 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
// both to tuples/lists and to other values, and in the latter case
|
||||
// the value will be treated as an implicit single-item tuple, or as
|
||||
// an empty tuple if the value is null.
|
||||
//nolint:staticcheck // QF1001: Demorgan's law wouldn't improve readability.
|
||||
autoUpgrade := !(sourceTy.IsTupleType() || sourceTy.IsListType() || sourceTy.IsSetType())
|
||||
|
||||
if sourceVal.IsNull() {
|
||||
@@ -1938,6 +1939,22 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
diags = append(diags, tyDiags...)
|
||||
return cty.ListValEmpty(ty.ElementType()).WithMarks(marks), diags
|
||||
}
|
||||
// Unfortunately it's possible for a nested splat on scalar values to
|
||||
// generate non-homogenously-typed vals, and we discovered this bad
|
||||
// interaction after the two conflicting behaviors were both
|
||||
// well-established so it isn't clear how to change them without
|
||||
// breaking existing code. Therefore we just make that an error for
|
||||
// now, to avoid crashing trying to constuct an impossible list.
|
||||
if !cty.CanListVal(vals) {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid nested splat expressions",
|
||||
Detail: "The second level of splat expression produced elements of different types, so it isn't possible to construct a valid list to represent the top-level result.\n\nConsider using a for expression instead, to produce a tuple-typed result which can therefore have non-homogenous element types.",
|
||||
Subject: e.Each.Range().Ptr(),
|
||||
Context: e.Range().Ptr(), // encourage a diagnostic renderer to also include the "source" part of the expression in its code snippet
|
||||
})
|
||||
return cty.DynamicVal, diags
|
||||
}
|
||||
return cty.ListVal(vals).WithMarks(marks), diags
|
||||
default:
|
||||
return cty.TupleVal(vals).WithMarks(marks), diags
|
||||
|
||||
+101
-7
@@ -16,16 +16,86 @@ import (
|
||||
type Operation struct {
|
||||
Impl function.Function
|
||||
Type cty.Type
|
||||
|
||||
// ShortCircuit is an optional callback for binary operations which, if set,
|
||||
// will be called with the result of evaluating the LHS and RHS expressions
|
||||
// and their individual diagnostics. The LHS and RHS values are guaranteed
|
||||
// to be unmarked and of the correct type.
|
||||
//
|
||||
// ShortCircuit may return cty.NilVal to allow evaluation to proceed as
|
||||
// normal, or it may return a non-nil value with diagnostics to return
|
||||
// before the main Impl is called. The returned diagnostics should match
|
||||
// the side of the Operation which was taken.
|
||||
ShortCircuit func(lhs, rhs cty.Value, lhsDiags, rhsDiags hcl.Diagnostics) (cty.Value, hcl.Diagnostics)
|
||||
}
|
||||
|
||||
var (
|
||||
OpLogicalOr = &Operation{
|
||||
Impl: stdlib.OrFunc,
|
||||
Type: cty.Bool,
|
||||
|
||||
ShortCircuit: func(lhs, rhs cty.Value, lhsDiags, rhsDiags hcl.Diagnostics) (cty.Value, hcl.Diagnostics) {
|
||||
switch {
|
||||
// if both are unknown, we don't short circuit anything
|
||||
case !lhs.IsKnown() && !rhs.IsKnown():
|
||||
// short-circuit left-to-right when encountering a good unknown
|
||||
// value and both are unknown.
|
||||
if !lhsDiags.HasErrors() {
|
||||
return cty.UnknownVal(cty.Bool).RefineNotNull(), lhsDiags
|
||||
}
|
||||
// If the LHS has an error, the RHS might too. Don't
|
||||
// short-circuit so both diags get collected.
|
||||
return cty.NilVal, nil
|
||||
|
||||
// for ||, a single true is the controlling condition
|
||||
case lhs.IsKnown() && lhs.True():
|
||||
return cty.True, lhsDiags
|
||||
case rhs.IsKnown() && rhs.True():
|
||||
return cty.True, rhsDiags
|
||||
|
||||
// if the opposing side is false we can't short-circuit based on
|
||||
// boolean logic, so an unknown becomes the controlling condition
|
||||
case !lhs.IsKnown() && rhs.False():
|
||||
return cty.UnknownVal(cty.Bool).RefineNotNull(), lhsDiags
|
||||
case !rhs.IsKnown() && lhs.False():
|
||||
return cty.UnknownVal(cty.Bool).RefineNotNull(), rhsDiags
|
||||
}
|
||||
|
||||
return cty.NilVal, nil
|
||||
},
|
||||
}
|
||||
OpLogicalAnd = &Operation{
|
||||
Impl: stdlib.AndFunc,
|
||||
Type: cty.Bool,
|
||||
|
||||
ShortCircuit: func(lhs, rhs cty.Value, lhsDiags, rhsDiags hcl.Diagnostics) (cty.Value, hcl.Diagnostics) {
|
||||
|
||||
switch {
|
||||
case !lhs.IsKnown() && !rhs.IsKnown():
|
||||
// short-circuit left-to-right when encountering a good unknown
|
||||
// value and both are unknown.
|
||||
if !lhsDiags.HasErrors() {
|
||||
return cty.UnknownVal(cty.Bool).RefineNotNull(), lhsDiags
|
||||
}
|
||||
// If the LHS has an error, the RHS might too. Don't
|
||||
// short-circuit so both diags get collected.
|
||||
return cty.NilVal, nil
|
||||
|
||||
// For &&, a single false is the controlling condition
|
||||
case lhs.IsKnown() && lhs.False():
|
||||
return cty.False, lhsDiags
|
||||
case rhs.IsKnown() && rhs.False():
|
||||
return cty.False, rhsDiags
|
||||
|
||||
// if the opposing side is true we can't short-circuit based on
|
||||
// boolean logic, so an unknown becomes the controlling condition
|
||||
case !lhs.IsKnown() && rhs.True():
|
||||
return cty.UnknownVal(cty.Bool).RefineNotNull(), lhsDiags
|
||||
case !rhs.IsKnown() && lhs.True():
|
||||
return cty.UnknownVal(cty.Bool).RefineNotNull(), rhsDiags
|
||||
}
|
||||
return cty.NilVal, nil
|
||||
},
|
||||
}
|
||||
OpLogicalNot = &Operation{
|
||||
Impl: stdlib.NotFunc,
|
||||
@@ -145,10 +215,6 @@ func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
givenLHSVal, lhsDiags := e.LHS.Value(ctx)
|
||||
givenRHSVal, rhsDiags := e.RHS.Value(ctx)
|
||||
diags = append(diags, lhsDiags...)
|
||||
diags = append(diags, rhsDiags...)
|
||||
|
||||
lhsVal, err := convert.Convert(givenLHSVal, lhsParam.Type)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
@@ -161,6 +227,8 @@ func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
|
||||
EvalContext: ctx,
|
||||
})
|
||||
}
|
||||
|
||||
givenRHSVal, rhsDiags := e.RHS.Value(ctx)
|
||||
rhsVal, err := convert.Convert(givenRHSVal, rhsParam.Type)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
@@ -174,12 +242,38 @@ func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
|
||||
})
|
||||
}
|
||||
|
||||
// diags so far only contains conversion errors, which should cover
|
||||
// incorrect parameter types.
|
||||
if diags.HasErrors() {
|
||||
// Don't actually try the call if we have errors already, since the
|
||||
// this will probably just produce a confusing duplicative diagnostic.
|
||||
// Add the rest of the diagnostic in case that helps the user, but keep
|
||||
// them separate as we continue for short-circuit handling.
|
||||
diags = append(diags, lhsDiags...)
|
||||
diags = append(diags, rhsDiags...)
|
||||
return cty.UnknownVal(e.Op.Type), diags
|
||||
}
|
||||
|
||||
lhsVal, lhsMarks := lhsVal.Unmark()
|
||||
rhsVal, rhsMarks := rhsVal.Unmark()
|
||||
|
||||
if e.Op.ShortCircuit != nil {
|
||||
forceResult, diags := e.Op.ShortCircuit(lhsVal, rhsVal, lhsDiags, rhsDiags)
|
||||
if forceResult != cty.NilVal {
|
||||
// It would be technically more correct to insert rhs diagnostics if
|
||||
// forceResult is not known since we didn't really short-circuit. That
|
||||
// would however not match the behavior of conditional expressions which
|
||||
// do drop all diagnostics from the unevaluated expressions
|
||||
return forceResult.WithMarks(lhsMarks, rhsMarks), diags
|
||||
}
|
||||
}
|
||||
|
||||
diags = append(diags, lhsDiags...)
|
||||
diags = append(diags, rhsDiags...)
|
||||
if diags.HasErrors() {
|
||||
// Don't actually try the call if we have errors, since the this will
|
||||
// probably just produce confusing duplicate diagnostics.
|
||||
return cty.UnknownVal(e.Op.Type).WithMarks(lhsMarks, rhsMarks), diags
|
||||
}
|
||||
|
||||
args := []cty.Value{lhsVal, rhsVal}
|
||||
result, err := impl.Call(args)
|
||||
if err != nil {
|
||||
@@ -195,7 +289,7 @@ func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
|
||||
return cty.UnknownVal(e.Op.Type), diags
|
||||
}
|
||||
|
||||
return result, diags
|
||||
return result.WithMarks(lhsMarks, rhsMarks), diags
|
||||
}
|
||||
|
||||
func (e *BinaryOpExpr) Range() hcl.Range {
|
||||
|
||||
+3
-5
@@ -184,11 +184,9 @@ func (e *TemplateJoinExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
|
||||
|
||||
if val.IsNull() {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid template interpolation value",
|
||||
Detail: fmt.Sprintf(
|
||||
"An iteration result is null. Cannot include a null value in a string template.",
|
||||
),
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid template interpolation value",
|
||||
Detail: "An iteration result is null. Cannot include a null value in a string template.",
|
||||
Subject: e.Range().Ptr(),
|
||||
Expression: e,
|
||||
EvalContext: ctx,
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ type Body struct {
|
||||
}
|
||||
|
||||
// Assert that *Body implements hcl.Body
|
||||
var assertBodyImplBody hcl.Body = &Body{}
|
||||
var _ hcl.Body = &Body{}
|
||||
|
||||
func (b *Body) walkChildNodes(w internalWalkFunc) {
|
||||
w(b.Attributes)
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ func Variables(expr Expression) []hcl.Traversal {
|
||||
},
|
||||
}
|
||||
|
||||
//nolint:errcheck // FIXME: Propogate diagnostics/errors upward.
|
||||
Walk(expr, walker)
|
||||
|
||||
return vars
|
||||
|
||||
+4
-7
@@ -37,7 +37,7 @@ func (f *File) Body() *Body {
|
||||
// The tokens first have a simple formatting pass applied that adjusts only
|
||||
// the spaces between them.
|
||||
func (f *File) WriteTo(wr io.Writer) (int64, error) {
|
||||
tokens := f.inTree.children.BuildTokens(nil)
|
||||
tokens := f.children.BuildTokens(nil)
|
||||
format(tokens)
|
||||
return tokens.WriteTo(wr)
|
||||
}
|
||||
@@ -47,6 +47,7 @@ func (f *File) WriteTo(wr io.Writer) (int64, error) {
|
||||
// the AST API, these will be reflected in the result.
|
||||
func (f *File) Bytes() []byte {
|
||||
buf := &bytes.Buffer{}
|
||||
//nolint:errcheck // FIXME: Propogate errors upward.
|
||||
f.WriteTo(buf)
|
||||
return buf.Bytes()
|
||||
}
|
||||
@@ -54,7 +55,6 @@ func (f *File) Bytes() []byte {
|
||||
type comments struct {
|
||||
leafNode
|
||||
|
||||
parent *node
|
||||
tokens Tokens
|
||||
}
|
||||
|
||||
@@ -71,8 +71,7 @@ func (c *comments) BuildTokens(to Tokens) Tokens {
|
||||
type identifier struct {
|
||||
leafNode
|
||||
|
||||
parent *node
|
||||
token *Token
|
||||
token *Token
|
||||
}
|
||||
|
||||
func newIdentifier(token *Token) *identifier {
|
||||
@@ -92,8 +91,7 @@ func (i *identifier) hasName(name string) bool {
|
||||
type number struct {
|
||||
leafNode
|
||||
|
||||
parent *node
|
||||
token *Token
|
||||
token *Token
|
||||
}
|
||||
|
||||
func newNumber(token *Token) *number {
|
||||
@@ -109,7 +107,6 @@ func (n *number) BuildTokens(to Tokens) Tokens {
|
||||
type quoted struct {
|
||||
leafNode
|
||||
|
||||
parent *node
|
||||
tokens Tokens
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -49,3 +49,9 @@ func (a *Attribute) init(name string, expr *Expression) {
|
||||
func (a *Attribute) Expr() *Expression {
|
||||
return a.expr.content.(*Expression)
|
||||
}
|
||||
|
||||
// setName updates the name of the attribute.
|
||||
func (a *Attribute) setName(name string) {
|
||||
nameObj := newIdentifier(newIdentToken(name))
|
||||
a.name = a.name.ReplaceWith(nameObj)
|
||||
}
|
||||
|
||||
+1
-1
@@ -122,7 +122,7 @@ func newBlockLabels(labels []string) *blockLabels {
|
||||
}
|
||||
|
||||
func (bl *blockLabels) Replace(newLabels []string) {
|
||||
bl.inTree.children.Clear()
|
||||
bl.children.Clear()
|
||||
bl.items.Clear()
|
||||
|
||||
for _, label := range newLabels {
|
||||
|
||||
+16
-1
@@ -43,7 +43,7 @@ func (b *Body) Clear() {
|
||||
}
|
||||
|
||||
func (b *Body) AppendUnstructuredTokens(ts Tokens) {
|
||||
b.inTree.children.Append(ts)
|
||||
b.children.Append(ts)
|
||||
}
|
||||
|
||||
// Attributes returns a new map of all of the attributes in the body, with
|
||||
@@ -103,6 +103,21 @@ func (b *Body) getAttributeNode(name string) *node {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenameAttribute changes the attribute named fromName to toName.
|
||||
// Takes no action if fromName is missing or there is already a
|
||||
// conflicting attribute called toName.
|
||||
//
|
||||
// Returns true if the rename succeeded.
|
||||
func (b *Body) RenameAttribute(fromName, toName string) bool {
|
||||
attr := b.GetAttribute(fromName)
|
||||
conflictingAttr := b.GetAttribute(toName)
|
||||
if attr == nil || conflictingAttr != nil {
|
||||
return false
|
||||
}
|
||||
attr.setName(toName)
|
||||
return true
|
||||
}
|
||||
|
||||
// FirstMatchingBlock returns a first matching block from the body that has the
|
||||
// given name and labels or returns nil if there is currently no matching
|
||||
// block.
|
||||
|
||||
+3
-2
@@ -429,9 +429,10 @@ func linesForFormat(tokens Tokens) []formatLine {
|
||||
}
|
||||
|
||||
func tokenIsNewline(tok *Token) bool {
|
||||
if tok.Type == hclsyntax.TokenNewline {
|
||||
switch tok.Type {
|
||||
case hclsyntax.TokenNewline:
|
||||
return true
|
||||
} else if tok.Type == hclsyntax.TokenComment {
|
||||
case hclsyntax.TokenComment:
|
||||
// Single line tokens (# and //) consume their terminating newline,
|
||||
// so we need to treat them as newline tokens as well.
|
||||
if len(tok.Bytes) > 0 && tok.Bytes[len(tok.Bytes)-1] == '\n' {
|
||||
|
||||
+12
-12
@@ -61,7 +61,7 @@ func parse(src []byte, filename string, start hcl.Pos) (*File, hcl.Diagnostics)
|
||||
body: root,
|
||||
}
|
||||
|
||||
nodes := ret.inTree.children
|
||||
nodes := ret.children
|
||||
nodes.Append(before.Tokens())
|
||||
nodes.AppendNode(root)
|
||||
nodes.Append(after.Tokens())
|
||||
@@ -242,7 +242,7 @@ func parseAttribute(nativeAttr *hclsyntax.Attribute, from, leadComments, lineCom
|
||||
attr := &Attribute{
|
||||
inTree: newInTree(),
|
||||
}
|
||||
children := attr.inTree.children
|
||||
children := attr.children
|
||||
|
||||
{
|
||||
cn := newNode(newComments(leadComments.Tokens()))
|
||||
@@ -293,7 +293,7 @@ func parseBlock(nativeBlock *hclsyntax.Block, from, leadComments, lineComments,
|
||||
block := &Block{
|
||||
inTree: newInTree(),
|
||||
}
|
||||
children := block.inTree.children
|
||||
children := block.children
|
||||
|
||||
{
|
||||
cn := newNode(newComments(leadComments.Tokens()))
|
||||
@@ -314,7 +314,7 @@ func parseBlock(nativeBlock *hclsyntax.Block, from, leadComments, lineComments,
|
||||
children.AppendNode(in)
|
||||
}
|
||||
|
||||
before, labelsNode, from := parseBlockLabels(nativeBlock, from)
|
||||
_, labelsNode, from := parseBlockLabels(nativeBlock, from)
|
||||
block.labels = labelsNode
|
||||
children.AppendNode(labelsNode)
|
||||
|
||||
@@ -377,7 +377,7 @@ func parseBlockLabels(nativeBlock *hclsyntax.Block, from inputTokens) (inputToke
|
||||
|
||||
func parseExpression(nativeExpr hclsyntax.Expression, from inputTokens) *node {
|
||||
expr := newExpression()
|
||||
children := expr.inTree.children
|
||||
children := expr.children
|
||||
|
||||
nativeVars := nativeExpr.Variables()
|
||||
|
||||
@@ -398,7 +398,7 @@ func parseExpression(nativeExpr hclsyntax.Expression, from inputTokens) *node {
|
||||
|
||||
func parseTraversal(nativeTraversal hcl.Traversal, from inputTokens) (before inputTokens, n *node, after inputTokens) {
|
||||
traversal := newTraversal()
|
||||
children := traversal.inTree.children
|
||||
children := traversal.children
|
||||
before, from, after = from.Partition(nativeTraversal.SourceRange())
|
||||
|
||||
stepAfter := from
|
||||
@@ -419,7 +419,7 @@ func parseTraversalStep(nativeStep hcl.Traverser, from inputTokens) (before inpu
|
||||
|
||||
case hcl.TraverseRoot, hcl.TraverseAttr:
|
||||
step := newTraverseName()
|
||||
children = step.inTree.children
|
||||
children = step.children
|
||||
before, from, after = from.Partition(nativeStep.SourceRange())
|
||||
inBefore, token, inAfter := from.PartitionTypeSingle(hclsyntax.TokenIdent)
|
||||
name := newIdentifier(token)
|
||||
@@ -430,7 +430,7 @@ func parseTraversalStep(nativeStep hcl.Traverser, from inputTokens) (before inpu
|
||||
|
||||
case hcl.TraverseIndex:
|
||||
step := newTraverseIndex()
|
||||
children = step.inTree.children
|
||||
children = step.children
|
||||
before, from, after = from.Partition(nativeStep.SourceRange())
|
||||
|
||||
if inBefore, dot, from, ok := from.PartitionTypeOk(hclsyntax.TokenDot); ok {
|
||||
@@ -521,10 +521,10 @@ func writerTokens(nativeTokens hclsyntax.Tokens) Tokens {
|
||||
// boundaries, such that the slice operator could be used to produce
|
||||
// three token sequences for before, within, and after respectively:
|
||||
//
|
||||
// start, end := partitionTokens(toks, rng)
|
||||
// before := toks[:start]
|
||||
// within := toks[start:end]
|
||||
// after := toks[end:]
|
||||
// start, end := partitionTokens(toks, rng)
|
||||
// before := toks[:start]
|
||||
// within := toks[start:end]
|
||||
// after := toks[end:]
|
||||
//
|
||||
// This works best when the range is aligned with token boundaries (e.g.
|
||||
// because it was produced in terms of the scanner's result) but if that isn't
|
||||
|
||||
+2
-1
@@ -19,7 +19,7 @@ func NewFile() *File {
|
||||
file := &File{
|
||||
inTree: newInTree(),
|
||||
}
|
||||
file.body = file.inTree.children.Append(body)
|
||||
file.body = file.children.Append(body)
|
||||
return file
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ func Format(src []byte) []byte {
|
||||
tokens := lexConfig(src)
|
||||
format(tokens)
|
||||
buf := &bytes.Buffer{}
|
||||
//nolint:errcheck // FIXME: Propogate errors upward.
|
||||
tokens.WriteTo(buf)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
+1
@@ -48,6 +48,7 @@ type Tokens []*Token
|
||||
|
||||
func (ts Tokens) Bytes() []byte {
|
||||
buf := &bytes.Buffer{}
|
||||
//nolint:errcheck // FIXME: Propogate errors upward.
|
||||
ts.WriteTo(buf)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
+3
-12
@@ -101,15 +101,6 @@ func parseValue(p *peeker) (node, hcl.Diagnostics) {
|
||||
}
|
||||
}
|
||||
|
||||
func tokenCanStartValue(tok token) bool {
|
||||
switch tok.Type {
|
||||
case tokenBraceO, tokenBrackO, tokenNumber, tokenString, tokenKeyword:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func parseObject(p *peeker) (node, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
@@ -375,7 +366,7 @@ func parseNumber(p *peeker) (node, hcl.Diagnostics) {
|
||||
{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid JSON number",
|
||||
Detail: fmt.Sprintf("There is a syntax error in the given JSON number."),
|
||||
Detail: "There is a syntax error in the given JSON number.",
|
||||
Subject: &tok.Range,
|
||||
},
|
||||
}
|
||||
@@ -394,7 +385,7 @@ func parseNumber(p *peeker) (node, hcl.Diagnostics) {
|
||||
{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid JSON number",
|
||||
Detail: fmt.Sprintf("There is a syntax error in the given JSON number."),
|
||||
Detail: "There is a syntax error in the given JSON number.",
|
||||
Subject: &tok.Range,
|
||||
},
|
||||
}
|
||||
@@ -448,7 +439,7 @@ func parseString(p *peeker) (node, hcl.Diagnostics) {
|
||||
{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid JSON string",
|
||||
Detail: fmt.Sprintf("There is a syntax error in the given JSON string."),
|
||||
Detail: "There is a syntax error in the given JSON string.",
|
||||
Subject: &errRange,
|
||||
Context: contextRange,
|
||||
},
|
||||
|
||||
+19
-11
@@ -5,7 +5,7 @@ package json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
@@ -92,20 +92,28 @@ func ParseExpressionWithStartPos(src []byte, filename string, start hcl.Pos) (hc
|
||||
// data from the given filename, passing the result to Parse if successful.
|
||||
//
|
||||
// If the file cannot be read, an error diagnostic with nil context is returned.
|
||||
func ParseFile(filename string) (*hcl.File, hcl.Diagnostics) {
|
||||
func ParseFile(filename string) (rf *hcl.File, diags hcl.Diagnostics) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, hcl.Diagnostics{
|
||||
{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to open file",
|
||||
Detail: fmt.Sprintf("The file %q could not be opened.", filename),
|
||||
},
|
||||
}
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to open file",
|
||||
Detail: fmt.Sprintf("The file %q could not be opened.", filename),
|
||||
})
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
defer func() {
|
||||
err := f.Close()
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: "Failed to close file",
|
||||
Detail: fmt.Sprintf("The file %q was opened, but an error occured while closing it.", filename),
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
src, err := ioutil.ReadAll(f)
|
||||
src, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, hcl.Diagnostics{
|
||||
{
|
||||
|
||||
+14
-16
@@ -107,23 +107,21 @@ func (mb mergedBodies) JustAttributes() (Attributes, Diagnostics) {
|
||||
diags = append(diags, thisDiags...)
|
||||
}
|
||||
|
||||
if thisAttrs != nil {
|
||||
for name, attr := range thisAttrs {
|
||||
if existing := attrs[name]; existing != nil {
|
||||
diags = diags.Append(&Diagnostic{
|
||||
Severity: DiagError,
|
||||
Summary: "Duplicate argument",
|
||||
Detail: fmt.Sprintf(
|
||||
"Argument %q was already set at %s",
|
||||
name, existing.NameRange.String(),
|
||||
),
|
||||
Subject: &attr.NameRange,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
attrs[name] = attr
|
||||
for name, attr := range thisAttrs {
|
||||
if existing := attrs[name]; existing != nil {
|
||||
diags = diags.Append(&Diagnostic{
|
||||
Severity: DiagError,
|
||||
Summary: "Duplicate argument",
|
||||
Detail: fmt.Sprintf(
|
||||
"Argument %q was already set at %s",
|
||||
name, existing.NameRange.String(),
|
||||
),
|
||||
Subject: &attr.NameRange,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
attrs[name] = attr
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -195,9 +195,6 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
|
||||
},
|
||||
}
|
||||
}
|
||||
if !collection.IsKnown() {
|
||||
return cty.DynamicVal.WithSameMarks(collection), nil
|
||||
}
|
||||
if !key.IsKnown() {
|
||||
return cty.DynamicVal.WithSameMarks(collection), nil
|
||||
}
|
||||
@@ -225,6 +222,10 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
if !collection.IsKnown() {
|
||||
return cty.UnknownVal(ty.AttributeType(attrName)).WithSameMarks(collection), nil
|
||||
}
|
||||
|
||||
return collection.GetAttr(attrName), nil
|
||||
|
||||
case ty.IsSetType():
|
||||
|
||||
+11
-11
@@ -74,7 +74,7 @@ func RelTraversalForExpr(expr Expression) (Traversal, Diagnostics) {
|
||||
// For example, the following attribute has an expression that would produce
|
||||
// the keyword "foo":
|
||||
//
|
||||
// example = foo
|
||||
// example = foo
|
||||
//
|
||||
// This function is a variant of AbsTraversalForExpr, which uses the same
|
||||
// interface on the given expression. This helper constrains the result
|
||||
@@ -84,16 +84,16 @@ func RelTraversalForExpr(expr Expression) (Traversal, Diagnostics) {
|
||||
// situations where one of a fixed set of keywords is required and arbitrary
|
||||
// expressions are not allowed:
|
||||
//
|
||||
// switch hcl.ExprAsKeyword(expr) {
|
||||
// case "allow":
|
||||
// // (take suitable action for keyword "allow")
|
||||
// case "deny":
|
||||
// // (take suitable action for keyword "deny")
|
||||
// default:
|
||||
// diags = append(diags, &hcl.Diagnostic{
|
||||
// // ... "invalid keyword" diagnostic message ...
|
||||
// })
|
||||
// }
|
||||
// switch hcl.ExprAsKeyword(expr) {
|
||||
// case "allow":
|
||||
// // (take suitable action for keyword "allow")
|
||||
// case "deny":
|
||||
// // (take suitable action for keyword "deny")
|
||||
// default:
|
||||
// diags = append(diags, &hcl.Diagnostic{
|
||||
// // ... "invalid keyword" diagnostic message ...
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// The above approach will generate the same message for both the use of an
|
||||
// unrecognized keyword and for not using a keyword at all, which is usually
|
||||
|
||||
+18
-8
@@ -5,6 +5,8 @@ import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const nbsp = 0xA0
|
||||
|
||||
// WrapString wraps the given string within lim width in characters.
|
||||
//
|
||||
// Wrapping is currently naive and only happens at white-space. A future
|
||||
@@ -18,50 +20,58 @@ func WrapString(s string, lim uint) string {
|
||||
|
||||
var current uint
|
||||
var wordBuf, spaceBuf bytes.Buffer
|
||||
var wordBufLen, spaceBufLen uint
|
||||
|
||||
for _, char := range s {
|
||||
if char == '\n' {
|
||||
if wordBuf.Len() == 0 {
|
||||
if current+uint(spaceBuf.Len()) > lim {
|
||||
if current+spaceBufLen > lim {
|
||||
current = 0
|
||||
} else {
|
||||
current += uint(spaceBuf.Len())
|
||||
current += spaceBufLen
|
||||
spaceBuf.WriteTo(buf)
|
||||
}
|
||||
spaceBuf.Reset()
|
||||
spaceBufLen = 0
|
||||
} else {
|
||||
current += uint(spaceBuf.Len() + wordBuf.Len())
|
||||
current += spaceBufLen + wordBufLen
|
||||
spaceBuf.WriteTo(buf)
|
||||
spaceBuf.Reset()
|
||||
spaceBufLen = 0
|
||||
wordBuf.WriteTo(buf)
|
||||
wordBuf.Reset()
|
||||
wordBufLen = 0
|
||||
}
|
||||
buf.WriteRune(char)
|
||||
current = 0
|
||||
} else if unicode.IsSpace(char) {
|
||||
} else if unicode.IsSpace(char) && char != nbsp {
|
||||
if spaceBuf.Len() == 0 || wordBuf.Len() > 0 {
|
||||
current += uint(spaceBuf.Len() + wordBuf.Len())
|
||||
current += spaceBufLen + wordBufLen
|
||||
spaceBuf.WriteTo(buf)
|
||||
spaceBuf.Reset()
|
||||
spaceBufLen = 0
|
||||
wordBuf.WriteTo(buf)
|
||||
wordBuf.Reset()
|
||||
wordBufLen = 0
|
||||
}
|
||||
|
||||
spaceBuf.WriteRune(char)
|
||||
spaceBufLen++
|
||||
} else {
|
||||
|
||||
wordBuf.WriteRune(char)
|
||||
wordBufLen++
|
||||
|
||||
if current+uint(spaceBuf.Len()+wordBuf.Len()) > lim && uint(wordBuf.Len()) < lim {
|
||||
if current+wordBufLen+spaceBufLen > lim && wordBufLen < lim {
|
||||
buf.WriteRune('\n')
|
||||
current = 0
|
||||
spaceBuf.Reset()
|
||||
spaceBufLen = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if wordBuf.Len() == 0 {
|
||||
if current+uint(spaceBuf.Len()) <= lim {
|
||||
if current+spaceBufLen <= lim {
|
||||
spaceBuf.WriteTo(buf)
|
||||
}
|
||||
} else {
|
||||
|
||||
+8
-8
@@ -14,7 +14,7 @@ type CapsuleOps struct {
|
||||
// corresponding type. Conventionally this should return a string
|
||||
// representation of an expression that would produce an equivalent
|
||||
// value.
|
||||
GoString func(val interface{}) string
|
||||
GoString func(val any) string
|
||||
|
||||
// TypeGoString provides the GoString implementation for the corresponding
|
||||
// capsule type itself.
|
||||
@@ -37,7 +37,7 @@ type CapsuleOps struct {
|
||||
//
|
||||
// If RawEquals is nil then Equals must also be nil, selecting the default
|
||||
// pointer-identity comparison instead.
|
||||
Equals func(a, b interface{}) Value
|
||||
Equals func(a, b any) Value
|
||||
|
||||
// RawEquals provides the implementation of the RawEquals operation.
|
||||
// This is called only with known, non-null values of the corresponding
|
||||
@@ -47,7 +47,7 @@ type CapsuleOps struct {
|
||||
//
|
||||
// If RawEquals is nil, values of the corresponding type are compared by
|
||||
// pointer identity of the encapsulated value.
|
||||
RawEquals func(a, b interface{}) bool
|
||||
RawEquals func(a, b any) bool
|
||||
|
||||
// HashKey provides a hashing function for values of the corresponding
|
||||
// capsule type. If defined, cty will use the resulting hashes as part
|
||||
@@ -59,7 +59,7 @@ type CapsuleOps struct {
|
||||
// RawEquals to return true when given those values. If a given type
|
||||
// does not uphold that assumption then sets including this type will
|
||||
// not behave correctly.
|
||||
HashKey func(v interface{}) string
|
||||
HashKey func(v any) string
|
||||
|
||||
// ConversionFrom can provide conversions from the corresponding type to
|
||||
// some other type when values of the corresponding type are used with
|
||||
@@ -68,7 +68,7 @@ type CapsuleOps struct {
|
||||
// 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)
|
||||
ConversionFrom func(src Type) func(any, Path) (Value, error)
|
||||
|
||||
// ConversionTo can provide conversions to the corresponding type from
|
||||
// some other type when values of the corresponding type are used with
|
||||
@@ -77,7 +77,7 @@ type CapsuleOps struct {
|
||||
// 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)
|
||||
ConversionTo func(dst Type) func(Value, Path) (any, error)
|
||||
|
||||
// ExtensionData is an extension point for applications that wish to
|
||||
// create their own extension features using capsule types.
|
||||
@@ -99,7 +99,7 @@ type CapsuleOps struct {
|
||||
// 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{}
|
||||
ExtensionData func(key any) any
|
||||
}
|
||||
|
||||
// noCapsuleOps is a pointer to a CapsuleOps with no functions set, which
|
||||
@@ -135,7 +135,7 @@ func (ty Type) CapsuleOps() *CapsuleOps {
|
||||
// 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{} {
|
||||
func (ty Type) CapsuleExtensionData(key any) any {
|
||||
ops := ty.CapsuleOps()
|
||||
if ops.ExtensionData == nil {
|
||||
return nil
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ 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 {
|
||||
func conversionToCapsule(inTy, outTy cty.Type, fn func(inTy cty.Type) func(cty.Value, cty.Path) (any, error)) conversion {
|
||||
rawConv := fn(inTy)
|
||||
if rawConv == nil {
|
||||
return nil
|
||||
@@ -19,7 +19,7 @@ func conversionToCapsule(inTy, outTy cty.Type, fn func(inTy cty.Type) func(cty.V
|
||||
}
|
||||
}
|
||||
|
||||
func conversionFromCapsule(inTy, outTy cty.Type, fn func(outTy cty.Type) func(interface{}, cty.Path) (cty.Value, error)) conversion {
|
||||
func conversionFromCapsule(inTy, outTy cty.Type, fn func(outTy cty.Type) func(any, cty.Path) (cty.Value, error)) conversion {
|
||||
rawConv := fn(outTy)
|
||||
if rawConv == nil {
|
||||
return nil
|
||||
|
||||
+55
@@ -53,6 +53,9 @@ func MismatchMessage(got, want cty.Type) string {
|
||||
case got.IsCollectionType() && want.IsCollectionType():
|
||||
return mismatchMessageCollectionsFromCollections(got, want)
|
||||
|
||||
case !typesAreLikelyToCauseConfusion(got, want):
|
||||
return fmt.Sprintf("%s required, but have %s", want.FriendlyName(), got.FriendlyName())
|
||||
|
||||
default:
|
||||
// If we have nothing better to say, we'll just state what was required.
|
||||
return want.FriendlyNameForConstraint() + " required"
|
||||
@@ -224,3 +227,55 @@ func mismatchMessageCollectionsFromCollections(got, want cty.Type) string {
|
||||
}
|
||||
return fmt.Sprintf("incorrect %s: %s", noun, MismatchMessage(gotEty, wantEty))
|
||||
}
|
||||
|
||||
func typesAreLikelyToCauseConfusion(got, want cty.Type) bool {
|
||||
// NOTE: This function is intended to be used as the penultemate test
|
||||
// in MismatchMessage, and so it intentionally does not address
|
||||
// combinations that are already addressed by the more specific
|
||||
// earlier tests.
|
||||
if gotP, wantP := got.IsPrimitiveType(), want.IsPrimitiveType(); gotP != wantP {
|
||||
// There's never any situation where a primitive type could successfully
|
||||
// convert to a non-primitive one, so this is very unlikely to be
|
||||
// intended as an automatic type conversion. This case is doing most
|
||||
// of the useful work of this function, allowing us to report such
|
||||
// things as "string required, but got tuple".
|
||||
return false
|
||||
}
|
||||
if want == cty.String {
|
||||
// All of the primitive types can always successfully convert to
|
||||
// string, regardless of value.
|
||||
return false
|
||||
}
|
||||
if (want == cty.Bool && got == cty.Number) || (got == cty.Bool && want == cty.Number) {
|
||||
// There are no automatic conversions between bool and number, so
|
||||
// describing both directions of this is unlikely to cause confusion.
|
||||
return false
|
||||
}
|
||||
if got.IsCollectionType() && want.IsCollectionType() && !got.ElementType().HasDynamicTypes() && want.ElementType().HasDynamicTypes() {
|
||||
// Describing mismatches of collection element types is helpful
|
||||
// when we have specific types to report, but confusing when
|
||||
// there are any inexact types involved because readers tend to
|
||||
// then think the inexactness of the element type is the problem,
|
||||
// when it's far more likely to be the collection type kind that's
|
||||
// causing the problem.
|
||||
return false
|
||||
}
|
||||
if got.IsTupleType() && want.IsObjectType() || got.IsObjectType() && got.IsTupleType() {
|
||||
// The structural type kinds get described just by their bare names,
|
||||
// "tuple" or "object", and so they are not confusing when they
|
||||
// differ. They _are_ potentially confusing when we have matching kinds
|
||||
// but different element types within, but thankfully that's most
|
||||
// often handled by one of the earlier cases in MismatchMessage.
|
||||
return false
|
||||
}
|
||||
// We are intentionally a little more vague for string-to-number and
|
||||
// string-to-bool conversions because the rules for those are a little
|
||||
// too subtle to be described purely as "need X but want Y", and historical
|
||||
// experience shows that describing it that way causes folks to focus
|
||||
// on the wrong problem (their value is wrong, not their type).
|
||||
|
||||
// If we find that we're getting here in additional specific cases where
|
||||
// it would be more helpful than confusing to report both types then
|
||||
// we'll add additional rules here in a future version.
|
||||
return true
|
||||
}
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
// Package ctymarks contains some ancillary types, values, and constants for
|
||||
// use with mark-related parts of the main cty package.
|
||||
package ctymarks
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package ctymarks
|
||||
|
||||
// WrangleAction values each describe an action to be taken for a particular
|
||||
// mark at a particular path that has been visited by a [WrangleFunc].
|
||||
//
|
||||
// A nil value of this type represents taking no action at all, and thereby
|
||||
// potentially allowing other later functions to decide an action instead.
|
||||
type WrangleAction interface {
|
||||
wrangleAction()
|
||||
}
|
||||
|
||||
type simpleWrangleAction rune
|
||||
|
||||
func (w simpleWrangleAction) wrangleAction() {}
|
||||
|
||||
// WrangleKeep is a [WrangleAction] that indicates that the given mark
|
||||
// should be retained at the given path and that no subsequent wrangle functions
|
||||
// in the same operation should be given an opportunity to visit the same mark.
|
||||
const WrangleKeep = simpleWrangleAction('k')
|
||||
|
||||
// WrangleDrop is a [WrangleAction] that indicates that the given mark
|
||||
// should be dropped at the given path, and that no subsequent wrangle functions
|
||||
// in the same operation should be given an opportunity to visit the same mark.
|
||||
const WrangleDrop = simpleWrangleAction('d')
|
||||
|
||||
// WrangleExpand is a [WrangleAction] that indicates that the given mark
|
||||
// should be transferred to the top-level value that the current mark wrangling
|
||||
// process is operating on.
|
||||
//
|
||||
// This effectively means that the mark then applies to all parts of the
|
||||
// original top-level value, rather than to only a small part of the nested
|
||||
// data structure within it.
|
||||
const WrangleExpand = simpleWrangleAction('e')
|
||||
|
||||
// WrangleReplace returns a [WrangleAction] that indicates that the given
|
||||
// mark should be removed and the given mark inserted in its place.
|
||||
//
|
||||
// This could be useful when values must cross between systems that use
|
||||
// different marks to represent similar concepts, for example.
|
||||
func WrangleReplace(newMark any) WrangleAction {
|
||||
if newMark == nil {
|
||||
panic("ctymarks.WrangleReplace with nil mark")
|
||||
}
|
||||
return wrangleReplaceAction{newMark}
|
||||
}
|
||||
|
||||
// WrangleReplaceMark returns the new mark for the given action if it was
|
||||
// created using [WrangleReplace], or nil if it is any other kind of action.
|
||||
func WrangleReplaceMark(action WrangleAction) any {
|
||||
replace, _ := action.(wrangleReplaceAction)
|
||||
return replace.newMark
|
||||
}
|
||||
|
||||
type wrangleReplaceAction struct {
|
||||
newMark any
|
||||
}
|
||||
|
||||
func (w wrangleReplaceAction) wrangleAction() {}
|
||||
+15
-15
@@ -11,11 +11,11 @@ import (
|
||||
//
|
||||
// Its usage pattern is as follows:
|
||||
//
|
||||
// it := val.ElementIterator()
|
||||
// for it.Next() {
|
||||
// key, val := it.Element()
|
||||
// // ...
|
||||
// }
|
||||
// it := val.ElementIterator()
|
||||
// for it.Next() {
|
||||
// key, val := it.Element()
|
||||
// // ...
|
||||
// }
|
||||
type ElementIterator interface {
|
||||
Next() bool
|
||||
Element() (key Value, value Value)
|
||||
@@ -46,13 +46,13 @@ func elementIterator(val Value) ElementIterator {
|
||||
case val.ty.IsListType():
|
||||
return &listElementIterator{
|
||||
ety: val.ty.ElementType(),
|
||||
vals: val.v.([]interface{}),
|
||||
vals: val.v.([]any),
|
||||
idx: -1,
|
||||
}
|
||||
case val.ty.IsMapType():
|
||||
// We iterate the keys in a predictable lexicographical order so
|
||||
// that results will always be stable given the same input map.
|
||||
rawMap := val.v.(map[string]interface{})
|
||||
rawMap := val.v.(map[string]any)
|
||||
keys := make([]string, 0, len(rawMap))
|
||||
for key := range rawMap {
|
||||
keys = append(keys, key)
|
||||
@@ -66,7 +66,7 @@ func elementIterator(val Value) ElementIterator {
|
||||
idx: -1,
|
||||
}
|
||||
case val.ty.IsSetType():
|
||||
rawSet := val.v.(set.Set[interface{}])
|
||||
rawSet := val.v.(set.Set[any])
|
||||
return &setElementIterator{
|
||||
ety: val.ty.ElementType(),
|
||||
setIt: rawSet.Iterator(),
|
||||
@@ -74,7 +74,7 @@ func elementIterator(val Value) ElementIterator {
|
||||
case val.ty.IsTupleType():
|
||||
return &tupleElementIterator{
|
||||
etys: val.ty.TupleElementTypes(),
|
||||
vals: val.v.([]interface{}),
|
||||
vals: val.v.([]any),
|
||||
idx: -1,
|
||||
}
|
||||
case val.ty.IsObjectType():
|
||||
@@ -89,7 +89,7 @@ func elementIterator(val Value) ElementIterator {
|
||||
|
||||
return &objectElementIterator{
|
||||
atys: atys,
|
||||
vals: val.v.(map[string]interface{}),
|
||||
vals: val.v.(map[string]any),
|
||||
attrNames: keys,
|
||||
idx: -1,
|
||||
}
|
||||
@@ -100,7 +100,7 @@ func elementIterator(val Value) ElementIterator {
|
||||
|
||||
type listElementIterator struct {
|
||||
ety Type
|
||||
vals []interface{}
|
||||
vals []any
|
||||
idx int
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ func (it *listElementIterator) Next() bool {
|
||||
|
||||
type mapElementIterator struct {
|
||||
ety Type
|
||||
vals map[string]interface{}
|
||||
vals map[string]any
|
||||
keys []string
|
||||
idx int
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func (it *mapElementIterator) Next() bool {
|
||||
|
||||
type setElementIterator struct {
|
||||
ety Type
|
||||
setIt *set.Iterator[interface{}]
|
||||
setIt *set.Iterator[any]
|
||||
}
|
||||
|
||||
func (it *setElementIterator) Element() (Value, Value) {
|
||||
@@ -156,7 +156,7 @@ func (it *setElementIterator) Next() bool {
|
||||
|
||||
type tupleElementIterator struct {
|
||||
etys []Type
|
||||
vals []interface{}
|
||||
vals []any
|
||||
idx int
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ func (it *tupleElementIterator) Next() bool {
|
||||
|
||||
type objectElementIterator struct {
|
||||
atys map[string]Type
|
||||
vals map[string]interface{}
|
||||
vals map[string]any
|
||||
attrNames []string
|
||||
idx int
|
||||
}
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ type PathError struct {
|
||||
Path Path
|
||||
}
|
||||
|
||||
func errorf(path Path, f string, args ...interface{}) error {
|
||||
func errorf(path Path, f string, args ...any) error {
|
||||
// We need to copy the Path because often our caller builds it by
|
||||
// continually mutating the same underlying buffer.
|
||||
sPath := make(Path, len(path))
|
||||
@@ -25,7 +25,7 @@ func errorf(path Path, f string, args ...interface{}) error {
|
||||
// NewErrorf creates a new PathError for the current path by passing the
|
||||
// given format and arguments to fmt.Errorf and then wrapping the result
|
||||
// similarly to NewError.
|
||||
func (p Path) NewErrorf(f string, args ...interface{}) error {
|
||||
func (p Path) NewErrorf(f string, args ...any) error {
|
||||
return errorf(p, f, args...)
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -15,7 +15,7 @@ type ArgError struct {
|
||||
Index int
|
||||
}
|
||||
|
||||
func NewArgErrorf(i int, f string, args ...interface{}) error {
|
||||
func NewArgErrorf(i int, f string, args ...any) error {
|
||||
return ArgError{
|
||||
error: fmt.Errorf(f, args...),
|
||||
Index: i,
|
||||
@@ -34,11 +34,11 @@ func NewArgError(i int, err error) error {
|
||||
// into a normal error so that callers (expected to be language runtimes)
|
||||
// are freed from having to deal with panics in buggy functions.
|
||||
type PanicError struct {
|
||||
Value interface{}
|
||||
Value any
|
||||
Stack []byte
|
||||
}
|
||||
|
||||
func errorForPanic(val interface{}) error {
|
||||
func errorForPanic(val any) error {
|
||||
return PanicError{
|
||||
Value: val,
|
||||
Stack: debug.Stack(),
|
||||
|
||||
+2
-2
@@ -11,12 +11,12 @@ import (
|
||||
var valueType = reflect.TypeOf(cty.Value{})
|
||||
var typeType = reflect.TypeOf(cty.Type{})
|
||||
|
||||
var setType = reflect.TypeOf(set.Set[interface{}]{})
|
||||
var setType = reflect.TypeOf(set.Set[any]{})
|
||||
|
||||
var bigFloatType = reflect.TypeOf(big.Float{})
|
||||
var bigIntType = reflect.TypeOf(big.Int{})
|
||||
|
||||
var emptyInterfaceType = reflect.TypeOf(interface{}(nil))
|
||||
var emptyInterfaceType = reflect.TypeOf(any(nil))
|
||||
|
||||
var stringType = reflect.TypeOf("")
|
||||
|
||||
|
||||
+6
-6
@@ -23,7 +23,7 @@ import (
|
||||
// presented from Go's perspective. These messages are thus not appropriate
|
||||
// for display to end-users. An error returned from ToCtyValue represents a
|
||||
// bug in the calling program, not user error.
|
||||
func ToCtyValue(val interface{}, ty cty.Type) (cty.Value, error) {
|
||||
func ToCtyValue(val any, ty cty.Type) (cty.Value, error) {
|
||||
// 'path' starts off as empty but will grow for each level of recursive
|
||||
// call we make, so by the time toCtyValue returns it is likely to have
|
||||
// unused capacity on the end of it, depending on how deeply-recursive
|
||||
@@ -268,7 +268,7 @@ func toCtySet(val reflect.Value, ety cty.Type, path cty.Path) (cty.Value, error)
|
||||
return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Type(), cty.Set(ety))
|
||||
}
|
||||
|
||||
rawSet := val.Interface().(set.Set[interface{}])
|
||||
rawSet := val.Interface().(set.Set[any])
|
||||
inVals := rawSet.Values()
|
||||
|
||||
if len(inVals) == 0 {
|
||||
@@ -528,10 +528,10 @@ func toCtyPassthrough(wrappedVal reflect.Value, wantTy cty.Type, path cty.Path)
|
||||
// toCtyUnwrapPointer is a helper for dealing with Go pointers. It has three
|
||||
// possible outcomes:
|
||||
//
|
||||
// - Given value isn't a pointer, so it's just returned as-is.
|
||||
// - Given value is a non-nil pointer, in which case it is dereferenced
|
||||
// and the result returned.
|
||||
// - Given value is a nil pointer, in which case an invalid value is returned.
|
||||
// - Given value isn't a pointer, so it's just returned as-is.
|
||||
// - Given value is a non-nil pointer, in which case it is dereferenced
|
||||
// and the result returned.
|
||||
// - Given value is a nil pointer, in which case an invalid value is returned.
|
||||
//
|
||||
// For nested pointer types, like **int, they are all dereferenced in turn
|
||||
// until a non-pointer value is found, or until a nil pointer is encountered.
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ import (
|
||||
//
|
||||
// The function will panic if given a non-pointer as the Go value target,
|
||||
// since that is considered to be a bug in the calling program.
|
||||
func FromCtyValue(val cty.Value, target interface{}) error {
|
||||
func FromCtyValue(val cty.Value, target any) error {
|
||||
tVal := reflect.ValueOf(target)
|
||||
if tVal.Kind() != reflect.Ptr {
|
||||
panic("target value is not a pointer")
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// ImpliedType takes an arbitrary Go value (as an interface{}) and attempts
|
||||
// ImpliedType takes an arbitrary Go value (as an any) and attempts
|
||||
// to find a suitable cty.Type instance that could be used for a conversion
|
||||
// with ToCtyValue.
|
||||
//
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
// In particular, ImpliedType will never use capsule types in its returned
|
||||
// type, because it cannot know the capsule types supported by the calling
|
||||
// program.
|
||||
func ImpliedType(gv interface{}) (cty.Type, error) {
|
||||
func ImpliedType(gv any) (cty.Type, error) {
|
||||
rt := reflect.TypeOf(gv)
|
||||
var path cty.Path
|
||||
return impliedType(rt, path)
|
||||
|
||||
+91
-44
@@ -2,7 +2,10 @@ package cty
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"strings"
|
||||
|
||||
"github.com/zclconf/go-cty/cty/ctymarks"
|
||||
)
|
||||
|
||||
// marker is an internal wrapper type used to add special "marks" to values.
|
||||
@@ -18,19 +21,19 @@ import (
|
||||
// an application that never marks a value does not need to worry about
|
||||
// encountering marked values.
|
||||
type marker struct {
|
||||
realV interface{}
|
||||
realV any
|
||||
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{}
|
||||
type ValueMarks map[any]struct{}
|
||||
|
||||
// NewValueMarks constructs a new ValueMarks set with the given mark values.
|
||||
//
|
||||
// If any of the arguments are already ValueMarks values then they'll be merged
|
||||
// into the result, rather than used directly as individual marks.
|
||||
func NewValueMarks(marks ...interface{}) ValueMarks {
|
||||
func NewValueMarks(marks ...any) ValueMarks {
|
||||
if len(marks) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -111,7 +114,7 @@ func (val Value) IsMarked() bool {
|
||||
}
|
||||
|
||||
// HasMark returns true if and only if the receiving value has the given mark.
|
||||
func (val Value) HasMark(mark interface{}) bool {
|
||||
func (val Value) HasMark(mark any) bool {
|
||||
if mr, ok := val.v.(marker); ok {
|
||||
_, ok := mr.marks[mark]
|
||||
return ok
|
||||
@@ -119,21 +122,68 @@ func (val Value) HasMark(mark interface{}) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// HasMarkDeep is like [HasMark] but also searches any values nested inside
|
||||
// the given value.
|
||||
func (val Value) HasMarkDeep(mark any) bool {
|
||||
for _, v := range DeepValues(val) {
|
||||
if v.HasMark(mark) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ValueMarksOfType returns an iterable sequence of any marks directly
|
||||
// associated with the given value that can be type-asserted to the given
|
||||
// type.
|
||||
func ValueMarksOfType[T any](v Value) iter.Seq[T] {
|
||||
return func(yield func(T) bool) {
|
||||
yieldValueMarksOfType(v, yield)
|
||||
}
|
||||
}
|
||||
|
||||
// ValueMarksOfTypeDeep is like [ValueMarksOfType] but also visits any values
|
||||
// nested inside the given value.
|
||||
//
|
||||
// The same value may be produced multiple times if multiple nested values are
|
||||
// marked with it.
|
||||
func ValueMarksOfTypeDeep[T any](v Value) iter.Seq[T] {
|
||||
return func(yield func(T) bool) {
|
||||
for _, v := range DeepValues(v) {
|
||||
if !yieldValueMarksOfType(v, yield) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func yieldValueMarksOfType[T any](v Value, yield func(T) bool) bool {
|
||||
mr, ok := v.v.(marker)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
for mark := range mr.marks {
|
||||
if v, ok := mark.(T); ok {
|
||||
if !yield(v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 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) {
|
||||
for _, v := range DeepValues(val) {
|
||||
if v.IsMarked() {
|
||||
ret = true
|
||||
return false, nil
|
||||
return true
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
return ret
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (val Value) assertUnmarked() {
|
||||
@@ -189,7 +239,7 @@ func (val Value) HasSameMarks(other Value) bool {
|
||||
//
|
||||
// An application that never calls this method does not need to worry about
|
||||
// handling marked values.
|
||||
func (val Value) Mark(mark interface{}) Value {
|
||||
func (val Value) Mark(mark any) Value {
|
||||
if _, ok := mark.(ValueMarks); ok {
|
||||
panic("cannot call Value.Mark with a ValueMarks value (use WithMarks instead)")
|
||||
}
|
||||
@@ -236,6 +286,11 @@ func (t *applyPathValueMarksTransformer) Exit(p Path, v Value) (Value, error) {
|
||||
// markers to particular paths and returns the marked
|
||||
// Value.
|
||||
func (val Value) MarkWithPaths(pvm []PathValueMarks) Value {
|
||||
if len(pvm) == 0 {
|
||||
// If we have no marks to apply then there's nothing to do, so we'll
|
||||
// just return the same value rather than wastefully rebuilding it.
|
||||
return val
|
||||
}
|
||||
ret, _ := TransformWithTransformer(val, &applyPathValueMarksTransformer{pvm})
|
||||
return ret
|
||||
}
|
||||
@@ -257,24 +312,6 @@ func (val Value) Unmark() (Value, ValueMarks) {
|
||||
}, marks
|
||||
}
|
||||
|
||||
type unmarkTransformer struct {
|
||||
pvm []PathValueMarks
|
||||
}
|
||||
|
||||
func (t *unmarkTransformer) Enter(p Path, v Value) (Value, error) {
|
||||
unmarkedVal, marks := v.Unmark()
|
||||
if len(marks) > 0 {
|
||||
path := make(Path, len(p), len(p)+1)
|
||||
copy(path, p)
|
||||
t.pvm = append(t.pvm, PathValueMarks{path, marks})
|
||||
}
|
||||
return unmarkedVal, nil
|
||||
}
|
||||
|
||||
func (t *unmarkTransformer) Exit(p Path, v Value) (Value, error) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// UnmarkDeep is similar to Unmark, but it works with an entire nested structure
|
||||
// rather than just the given value directly.
|
||||
//
|
||||
@@ -282,17 +319,12 @@ func (t *unmarkTransformer) Exit(p Path, v Value) (Value, error) {
|
||||
// the returned marks set includes the superset of all of the marks encountered
|
||||
// during the operation.
|
||||
func (val Value) UnmarkDeep() (Value, ValueMarks) {
|
||||
t := unmarkTransformer{}
|
||||
ret, _ := TransformWithTransformer(val, &t)
|
||||
|
||||
marks := make(ValueMarks)
|
||||
for _, pvm := range t.pvm {
|
||||
for m, s := range pvm.Marks {
|
||||
marks[m] = s
|
||||
}
|
||||
}
|
||||
|
||||
return ret, marks
|
||||
retMarks := make(ValueMarks)
|
||||
retVal, _ := val.WrangleMarksDeep(func(mark any, path Path) (ctymarks.WrangleAction, error) {
|
||||
retMarks[mark] = struct{}{}
|
||||
return ctymarks.WrangleDrop, nil
|
||||
})
|
||||
return retVal, retMarks
|
||||
}
|
||||
|
||||
// UnmarkDeepWithPaths is like UnmarkDeep, except it returns a slice
|
||||
@@ -300,9 +332,24 @@ func (val Value) UnmarkDeep() (Value, ValueMarks) {
|
||||
// a caller to know which marks are associated with which paths
|
||||
// in the Value.
|
||||
func (val Value) UnmarkDeepWithPaths() (Value, []PathValueMarks) {
|
||||
t := unmarkTransformer{}
|
||||
ret, _ := TransformWithTransformer(val, &t)
|
||||
return ret, t.pvm
|
||||
var pvm []PathValueMarks
|
||||
retVal, _ := val.WrangleMarksDeep(func(mark any, path Path) (ctymarks.WrangleAction, error) {
|
||||
if len(pvm) != 0 {
|
||||
// We'll try to modify the most recent item instead of adding
|
||||
// a new one, if the path hasn't changed.
|
||||
latest := &pvm[len(pvm)-1]
|
||||
if latest.Path.Equals(path) {
|
||||
latest.Marks[mark] = struct{}{}
|
||||
return ctymarks.WrangleDrop, nil
|
||||
}
|
||||
}
|
||||
pvm = append(pvm, PathValueMarks{
|
||||
Path: path.Copy(),
|
||||
Marks: NewValueMarks(mark),
|
||||
})
|
||||
return ctymarks.WrangleDrop, nil
|
||||
})
|
||||
return retVal, pvm
|
||||
}
|
||||
|
||||
func (val Value) unmarkForce() Value {
|
||||
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
package cty
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"iter"
|
||||
"maps"
|
||||
|
||||
"github.com/zclconf/go-cty/cty/ctymarks"
|
||||
)
|
||||
|
||||
// WrangleMarksDeep is a specialized variant of [Transform] that is focused
|
||||
// on interrogating and modifying any marks present throughout a data structure,
|
||||
// without modifying anything else about the value.
|
||||
//
|
||||
// Refer to the [WrangleFunc] documentation for more information. Each of
|
||||
// the provided functions is called in turn for each mark at each distinct path,
|
||||
// and the first function that returns a non-nil [ctymarks.WrangleAction] "wins"
|
||||
// and prevents any later ones from running for a particular mark/path pair.
|
||||
//
|
||||
// The implementation makes a best effort to avoid constructing new values
|
||||
// unless marks have actually changed, to keep this operation relatively cheap
|
||||
// in the presumed-common case where no marks are present at all.
|
||||
func (v Value) WrangleMarksDeep(wranglers ...WrangleFunc) (Value, error) {
|
||||
// This function is implemented in this package, rather than in the
|
||||
// separate "ctymarks", so that it can intrude into the unexported
|
||||
// internal details of [Value] to minimize overhead when no marks
|
||||
// are present at all.
|
||||
var path Path
|
||||
if v.IsKnown() && !v.Type().IsPrimitiveType() && !v.IsNull() {
|
||||
// If we have a known, non-null, non-primitive-typed value then we
|
||||
// can assume there will be at least a little nesting we need to
|
||||
// represent using our path, and so we'll preallocate some capacity
|
||||
// which we'll be able to share across all calls that are shallower
|
||||
// than this level of nesting.
|
||||
path = make(Path, 0, 4)
|
||||
}
|
||||
topMarks := make(ValueMarks)
|
||||
var errs []error
|
||||
new := wrangleMarksDeep(v, wranglers, path, topMarks, &errs)
|
||||
if new == NilVal {
|
||||
new = v // completely unchanged
|
||||
}
|
||||
var err error
|
||||
switch len(errs) {
|
||||
case 0:
|
||||
// nil err is fine, then
|
||||
case 1:
|
||||
err = errs[0]
|
||||
default:
|
||||
err = errors.Join(errs...)
|
||||
}
|
||||
return new.WithMarks(topMarks), err
|
||||
}
|
||||
|
||||
// wrangleMarksDeep is the main implementation of [WrangleMarksDeep], which
|
||||
// calls itself recursively to handle nested data structures.
|
||||
//
|
||||
// If the returned value is [NilVal] then that means that no changes were
|
||||
// needed to anything at or beneath that nesting level and so the caller should
|
||||
// just keep the original value exactly.
|
||||
//
|
||||
// Modifies topMarks and errs during traversal to collect (respectively) any
|
||||
// marks that caused [ctymarks.WrangleExpand] and and errors returned by
|
||||
// wrangle functions.
|
||||
func wrangleMarksDeep(v Value, wranglers []WrangleFunc, path Path, topMarks ValueMarks, errs *[]error) Value {
|
||||
var givenMarks, newMarks ValueMarks
|
||||
makeNewValue := false
|
||||
// The following is the same idea as [Value.Unmark], but implemented inline
|
||||
// here so that we can skip copying any existing ValueMarks that might
|
||||
// already be present, since we know we're not going to try to mutate it.
|
||||
if marked, ok := v.v.(marker); ok {
|
||||
v = Value{
|
||||
ty: v.ty,
|
||||
v: marked.realV,
|
||||
}
|
||||
givenMarks = marked.marks
|
||||
}
|
||||
|
||||
// We call this whenever we know we're returning a new value, to perform
|
||||
// a one-time copy of the given marks into a new marks map we can modify
|
||||
// and to set a flag to force us to construct a newly-marked value when
|
||||
// we return below.
|
||||
needNewValue := func() {
|
||||
if newMarks == nil && len(givenMarks) != 0 {
|
||||
newMarks = make(ValueMarks, len(givenMarks))
|
||||
maps.Copy(newMarks, givenMarks)
|
||||
}
|
||||
makeNewValue = true
|
||||
}
|
||||
|
||||
for mark := range givenMarks {
|
||||
Wranglers:
|
||||
for _, wrangler := range wranglers {
|
||||
action, err := wrangler(mark, path)
|
||||
if err != nil {
|
||||
if len(path) != 0 {
|
||||
err = path.NewError(err)
|
||||
}
|
||||
*errs = append(*errs, err)
|
||||
}
|
||||
switch action {
|
||||
case nil:
|
||||
continue Wranglers
|
||||
case ctymarks.WrangleKeep:
|
||||
break Wranglers
|
||||
case ctymarks.WrangleExpand:
|
||||
topMarks[mark] = struct{}{}
|
||||
break Wranglers
|
||||
case ctymarks.WrangleDrop:
|
||||
needNewValue()
|
||||
delete(newMarks, mark)
|
||||
break Wranglers
|
||||
default:
|
||||
newMark := ctymarks.WrangleReplaceMark(action)
|
||||
if newMark == nil {
|
||||
// Should not get here because these cases should be
|
||||
// exhaustive for all possible WrangleAction values.
|
||||
panic(fmt.Sprintf("unhandled WrangleAction %#v", action))
|
||||
}
|
||||
needNewValue()
|
||||
delete(newMarks, mark)
|
||||
newMarks[newMark] = struct{}{}
|
||||
break Wranglers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We're not going to make any further changes to our _direct_ marks
|
||||
// after this, so if we didn't already make a copy of the given marks
|
||||
// we can now safely alias our original set to reuse when we return.
|
||||
// (We might still construct a new value though, if we recurse into
|
||||
// a nested value that needs its own changes.)
|
||||
if newMarks == nil {
|
||||
newMarks = givenMarks // might still be nil if we didn't have any marks on entry
|
||||
}
|
||||
|
||||
// Now we'll visit nested values recursively, if any.
|
||||
// The cases below intentionally don't cover primitive types, set types,
|
||||
// or capsule types, because none of them can possibly have nested marks
|
||||
// inside. (For set types in particular, any marks on inner values get
|
||||
// aggregated on the top-level set itself during construction.)
|
||||
ty := v.Type()
|
||||
switch {
|
||||
case v.IsNull() || !v.IsKnown():
|
||||
// Can't recurse into null or unknown values, regardless of type,
|
||||
// so nothing to do here.
|
||||
|
||||
case ty.IsListType() || ty.IsTupleType():
|
||||
// These types both have the same internal representation, and we
|
||||
// know we're not going to change anything about the type, so we
|
||||
// can share the same implementation for both.
|
||||
l := v.LengthInt()
|
||||
if l == 0 {
|
||||
break // nothing to do for an empty container
|
||||
}
|
||||
|
||||
// We'll avoid allocating a new slice until we know we're going
|
||||
// to make a change.
|
||||
var newElems []any // as would appear in Value.v for all three of these types
|
||||
for i, innerV := range replaceKWithIdx(v.Elements()) {
|
||||
path := append(path, IndexStep{Key: NumberIntVal(int64(i))})
|
||||
newInnerV := wrangleMarksDeep(innerV, wranglers, path, topMarks, errs)
|
||||
if newInnerV != NilVal {
|
||||
needNewValue()
|
||||
if newElems == nil {
|
||||
// If this is the first change we've found then we need to
|
||||
// allocate our new elems array and retroactively copy
|
||||
// anything we previously skipped because it was unchanged.
|
||||
newElems = make([]any, i, l)
|
||||
copy(newElems, v.v.([]any))
|
||||
}
|
||||
newElems = append(newElems, newInnerV.v)
|
||||
} else if newElems != nil {
|
||||
// Once we've started building a new value we need to append
|
||||
// everything to it whether it's changed or not, but we can
|
||||
// reuse the unchanged element's internal value.
|
||||
newElems = append(newElems, innerV.v)
|
||||
}
|
||||
}
|
||||
if newElems != nil {
|
||||
// if we built a new array of elements then it should replace
|
||||
// the one from our input value.
|
||||
v.v = newElems
|
||||
}
|
||||
|
||||
case ty.IsMapType() || ty.IsObjectType():
|
||||
// These types both have the same internal representation, and we
|
||||
// know we're not going to change anything about the type, so we
|
||||
// can share the same implementation for both.
|
||||
l := v.LengthInt()
|
||||
if l == 0 {
|
||||
break // nothing to do for an empty container
|
||||
}
|
||||
|
||||
// We'll avoid allocating a new map until we know we're going to
|
||||
// make a change.
|
||||
var newElems map[string]any
|
||||
for keyV, innerV := range v.Elements() {
|
||||
var pathStep PathStep
|
||||
if ty.IsObjectType() {
|
||||
pathStep = GetAttrStep{Name: keyV.AsString()}
|
||||
} else {
|
||||
pathStep = IndexStep{Key: keyV}
|
||||
}
|
||||
path := append(path, pathStep)
|
||||
newInnerV := wrangleMarksDeep(innerV, wranglers, path, topMarks, errs)
|
||||
if newInnerV != NilVal {
|
||||
needNewValue()
|
||||
if newElems == nil {
|
||||
// If this is the first change we've found then we need to
|
||||
// allocate our new elems map and retroactively copy
|
||||
// everything from the original map before we overwrite
|
||||
// the elements that need to change.
|
||||
newElems = make(map[string]any, l)
|
||||
maps.Copy(newElems, v.v.(map[string]any))
|
||||
}
|
||||
newElems[keyV.AsString()] = newInnerV.v
|
||||
}
|
||||
}
|
||||
if newElems != nil {
|
||||
// if we built a new map of elements then it should replace
|
||||
// the one from our input value.
|
||||
v.v = newElems
|
||||
}
|
||||
}
|
||||
|
||||
if !makeNewValue {
|
||||
// We didn't make any changes to the marks, so we don't need to
|
||||
// construct a new value.
|
||||
return NilVal
|
||||
}
|
||||
return v.WithMarks(newMarks)
|
||||
}
|
||||
|
||||
// WrangleFunc is the signature of a callback function used to visit a
|
||||
// particular mark associated with a particular path within a value.
|
||||
//
|
||||
// [Path] values passed to successive calls to a [WrangleFunc] may share a
|
||||
// backing array, and so if the function wishes to retain a particular path
|
||||
// after it returns it must use [Path.Copy] to produce a copy in an unaliased
|
||||
// backing array.
|
||||
//
|
||||
// A function of this type must decide what change to make, if any, to the
|
||||
// presence of this mark at this location. Returning nil means to take no
|
||||
// action at all and to potentially allow other later functions of this
|
||||
// type to decide what to do instead.
|
||||
//
|
||||
// If the function returns an error then [Value.WrangleMarksDeep] collects it
|
||||
// and any other errors returned during traversal, automatically wraps in a
|
||||
// [cty.PathError] if not at the root, and returns an [`errors.Join`] of all
|
||||
// of the errors if there are more than one. The indicated action is still taken
|
||||
// and continued "wrangling" occurs as normal to visit other marks and other
|
||||
// paths.
|
||||
//
|
||||
// [cty.Value.WrangleMarksDeep], and this callback signature used with it,
|
||||
// are together designed with some assumptions that don't always hold but
|
||||
// have been common enough to make it seem worth supporting with a first-class
|
||||
// feature:
|
||||
//
|
||||
// - All of the different marks on any specific value are orthogonal to one
|
||||
// another, and so it's possible to decide an action for each one in
|
||||
// isolation.
|
||||
// - Marks within a data structure are orthogonal to the specific values they
|
||||
// are associated with, and so it's possible to decide an action without
|
||||
// knowning the value it's associated with. (Though it's possible in
|
||||
// principle to use the given path to retrieve more information when needed,
|
||||
// at the expense of some additional traversal overhead.)
|
||||
// - Most values have no marks at all and when marks are present there are
|
||||
// relatively few of them, and so it's worth making some extra effort to
|
||||
// handle the no-marks case cheaply even if it makes the marks-present case
|
||||
// a little more expensive.
|
||||
//
|
||||
// If any of these assumptions don't apply to your situation then this may not
|
||||
// be an appropriate solution. [cty.Transform] or [cty.TransformWithTransformer]
|
||||
// might serve as a more general alternative if you need more control.
|
||||
type WrangleFunc func(mark any, path Path) (ctymarks.WrangleAction, error)
|
||||
|
||||
func replaceKWithIdx[K any, V any](in iter.Seq2[K, V]) iter.Seq2[int, V] {
|
||||
return func(yield func(int, V) bool) {
|
||||
i := 0
|
||||
for _, v := range in {
|
||||
if !yield(i, v) {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -134,7 +134,7 @@ func init() {
|
||||
EmptyObject = Object(map[string]Type{})
|
||||
EmptyObjectVal = Value{
|
||||
ty: EmptyObject,
|
||||
v: map[string]interface{}{},
|
||||
v: map[string]any{},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -43,5 +43,5 @@ type OrderedRules[T any] interface {
|
||||
// Less returns true if and only if the first argument should sort before
|
||||
// the second argument. If the second argument should sort before the first
|
||||
// or if there is no defined order for the values, return false.
|
||||
Less(interface{}, interface{}) bool
|
||||
Less(any, any) bool
|
||||
}
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@ type ValueSet struct {
|
||||
// ValueSet is just a thin wrapper around a set.Set with our value-oriented
|
||||
// "rules" applied. We do this so that the caller can work in terms of
|
||||
// cty.Value objects even though the set internals use the raw values.
|
||||
s set.Set[interface{}]
|
||||
s set.Set[any]
|
||||
}
|
||||
|
||||
// NewValueSet creates and returns a new ValueSet with the given element type.
|
||||
@@ -29,7 +29,7 @@ func NewValueSet(ety Type) ValueSet {
|
||||
return newValueSet(set.NewSet(newSetRules(ety)))
|
||||
}
|
||||
|
||||
func newValueSet(s set.Set[interface{}]) ValueSet {
|
||||
func newValueSet(s set.Set[any]) ValueSet {
|
||||
return ValueSet{
|
||||
s: s,
|
||||
}
|
||||
|
||||
+6
-6
@@ -21,9 +21,9 @@ type setRules struct {
|
||||
Type Type
|
||||
}
|
||||
|
||||
var _ set.OrderedRules[interface{}] = setRules{}
|
||||
var _ set.OrderedRules[any] = setRules{}
|
||||
|
||||
func newSetRules(ety Type) set.Rules[interface{}] {
|
||||
func newSetRules(ety Type) set.Rules[any] {
|
||||
return setRules{ety}
|
||||
}
|
||||
|
||||
@@ -43,14 +43,14 @@ func (val Value) Hash() int {
|
||||
return int(crc32.ChecksumIEEE(hashBytes))
|
||||
}
|
||||
|
||||
func (r setRules) Hash(v interface{}) int {
|
||||
func (r setRules) Hash(v any) int {
|
||||
return Value{
|
||||
ty: r.Type,
|
||||
v: v,
|
||||
}.Hash()
|
||||
}
|
||||
|
||||
func (r setRules) Equivalent(v1 interface{}, v2 interface{}) bool {
|
||||
func (r setRules) Equivalent(v1 any, v2 any) bool {
|
||||
v1v := Value{
|
||||
ty: r.Type,
|
||||
v: v1,
|
||||
@@ -71,7 +71,7 @@ func (r setRules) Equivalent(v1 interface{}, v2 interface{}) bool {
|
||||
|
||||
// SameRules is only true if the other Rules instance is also a setRules struct,
|
||||
// and the types are considered equal.
|
||||
func (r setRules) SameRules(other set.Rules[interface{}]) bool {
|
||||
func (r setRules) SameRules(other set.Rules[any]) bool {
|
||||
rules, ok := other.(setRules)
|
||||
if !ok {
|
||||
return false
|
||||
@@ -82,7 +82,7 @@ func (r setRules) SameRules(other set.Rules[interface{}]) bool {
|
||||
|
||||
// Less is an implementation of set.OrderedRules so that we can iterate over
|
||||
// set elements in a consistent order, where such an order is possible.
|
||||
func (r setRules) Less(v1, v2 interface{}) bool {
|
||||
func (r setRules) Less(v1, v2 any) bool {
|
||||
v1v := Value{
|
||||
ty: r.Type,
|
||||
v: v1,
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ func init() {
|
||||
EmptyTuple = Tuple([]Type{})
|
||||
EmptyTupleVal = Value{
|
||||
ty: EmptyTuple,
|
||||
v: []interface{}{},
|
||||
v: []any{},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ type unknownType struct {
|
||||
// on the unknown value, but all unknown values start as totally unknown
|
||||
// and we will also typically lose all unknown value refinements when
|
||||
// round-tripping through serialization formats.
|
||||
var totallyUnknown interface{} = &unknownType{}
|
||||
var totallyUnknown any = &unknownType{}
|
||||
|
||||
// UnknownVal returns an Value that represents an unknown value of the given
|
||||
// type. Unknown values can be used to represent a value that is
|
||||
|
||||
+5
@@ -8,6 +8,11 @@ package cty
|
||||
// represent unknowns, such as JSON, as long as the caller does not need to
|
||||
// retain the unknown value information.
|
||||
func UnknownAsNull(val Value) Value {
|
||||
if val.IsMarked() {
|
||||
val, valMarks := val.Unmark()
|
||||
return UnknownAsNull(val).WithMarks(valMarks)
|
||||
}
|
||||
|
||||
ty := val.Type()
|
||||
switch {
|
||||
case val.IsNull():
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ package cty
|
||||
// rules than are offered by the built-in converter where necessary.
|
||||
type Value struct {
|
||||
ty Type
|
||||
v interface{}
|
||||
v any
|
||||
}
|
||||
|
||||
// Type returns the type of the value.
|
||||
|
||||
+10
-10
@@ -113,7 +113,7 @@ func NormalizeString(s string) string {
|
||||
// by the key names and value types in the given map.
|
||||
func ObjectVal(attrs map[string]Value) Value {
|
||||
attrTypes := make(map[string]Type, len(attrs))
|
||||
attrVals := make(map[string]interface{}, len(attrs))
|
||||
attrVals := make(map[string]any, len(attrs))
|
||||
|
||||
for attr, val := range attrs {
|
||||
attr = NormalizeString(attr)
|
||||
@@ -131,7 +131,7 @@ func ObjectVal(attrs map[string]Value) Value {
|
||||
// defined by the value types in the given slice.
|
||||
func TupleVal(elems []Value) Value {
|
||||
elemTypes := make([]Type, len(elems))
|
||||
elemVals := make([]interface{}, len(elems))
|
||||
elemVals := make([]any, len(elems))
|
||||
|
||||
for i, val := range elems {
|
||||
elemTypes[i] = val.ty
|
||||
@@ -156,7 +156,7 @@ func ListVal(vals []Value) Value {
|
||||
panic("must not call ListVal with empty slice")
|
||||
}
|
||||
elementType := DynamicPseudoType
|
||||
rawList := make([]interface{}, len(vals))
|
||||
rawList := make([]any, len(vals))
|
||||
|
||||
for i, val := range vals {
|
||||
if elementType == DynamicPseudoType {
|
||||
@@ -181,7 +181,7 @@ func ListVal(vals []Value) Value {
|
||||
func ListValEmpty(element Type) Value {
|
||||
return Value{
|
||||
ty: List(element),
|
||||
v: []interface{}{},
|
||||
v: []any{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ func MapVal(vals map[string]Value) Value {
|
||||
panic("must not call MapVal with empty map")
|
||||
}
|
||||
elementType := DynamicPseudoType
|
||||
rawMap := make(map[string]interface{}, len(vals))
|
||||
rawMap := make(map[string]any, len(vals))
|
||||
|
||||
for key, val := range vals {
|
||||
if elementType == DynamicPseudoType {
|
||||
@@ -236,7 +236,7 @@ func MapVal(vals map[string]Value) Value {
|
||||
func MapValEmpty(element Type) Value {
|
||||
return Value{
|
||||
ty: Map(element),
|
||||
v: map[string]interface{}{},
|
||||
v: map[string]any{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ func SetVal(vals []Value) Value {
|
||||
panic("must not call SetVal with empty slice")
|
||||
}
|
||||
elementType := DynamicPseudoType
|
||||
rawList := make([]interface{}, len(vals))
|
||||
rawList := make([]any, len(vals))
|
||||
var markSets []ValueMarks
|
||||
|
||||
for i, val := range vals {
|
||||
@@ -286,7 +286,7 @@ func SetVal(vals []Value) Value {
|
||||
rawList[i] = val.v
|
||||
}
|
||||
|
||||
rawVal := set.NewSetFromSlice(set.Rules[interface{}](setRules{elementType}), rawList)
|
||||
rawVal := set.NewSetFromSlice(set.Rules[any](setRules{elementType}), rawList)
|
||||
|
||||
return Value{
|
||||
ty: Set(elementType),
|
||||
@@ -333,7 +333,7 @@ func SetValFromValueSet(s ValueSet) Value {
|
||||
func SetValEmpty(element Type) Value {
|
||||
return Value{
|
||||
ty: Set(element),
|
||||
v: set.NewSet(set.Rules[interface{}](setRules{element})),
|
||||
v: set.NewSet(set.Rules[any](setRules{element})),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +344,7 @@ func SetValEmpty(element Type) Value {
|
||||
// This function will panic if the given type is not a capsule type, if
|
||||
// the given wrapVal is not compatible with the given capsule type, or if
|
||||
// wrapVal is not a pointer.
|
||||
func CapsuleVal(ty Type, wrapVal interface{}) Value {
|
||||
func CapsuleVal(ty Type, wrapVal any) Value {
|
||||
if !ty.IsCapsuleType() {
|
||||
panic("not a capsule type")
|
||||
}
|
||||
|
||||
+85
-63
@@ -2,6 +2,7 @@ package cty
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"math/big"
|
||||
|
||||
"github.com/zclconf/go-cty/cty/set"
|
||||
@@ -13,7 +14,7 @@ func (val Value) GoString() string {
|
||||
if val.IsMarked() {
|
||||
unVal, marks := val.Unmark()
|
||||
if len(marks) == 1 {
|
||||
var mark interface{}
|
||||
var mark any
|
||||
for m := range marks {
|
||||
mark = m
|
||||
}
|
||||
@@ -243,11 +244,11 @@ func (val Value) Equals(other Value) Value {
|
||||
for attr, aty := range oty.AttrTypes {
|
||||
lhs := Value{
|
||||
ty: aty,
|
||||
v: val.v.(map[string]interface{})[attr],
|
||||
v: val.v.(map[string]any)[attr],
|
||||
}
|
||||
rhs := Value{
|
||||
ty: aty,
|
||||
v: other.v.(map[string]interface{})[attr],
|
||||
v: other.v.(map[string]any)[attr],
|
||||
}
|
||||
eq := lhs.Equals(rhs)
|
||||
if !eq.IsKnown() {
|
||||
@@ -264,11 +265,11 @@ func (val Value) Equals(other Value) Value {
|
||||
for i, ety := range tty.ElemTypes {
|
||||
lhs := Value{
|
||||
ty: ety,
|
||||
v: val.v.([]interface{})[i],
|
||||
v: val.v.([]any)[i],
|
||||
}
|
||||
rhs := Value{
|
||||
ty: ety,
|
||||
v: other.v.([]interface{})[i],
|
||||
v: other.v.([]any)[i],
|
||||
}
|
||||
eq := lhs.Equals(rhs)
|
||||
if !eq.IsKnown() {
|
||||
@@ -281,16 +282,16 @@ func (val Value) Equals(other Value) Value {
|
||||
}
|
||||
case ty.IsListType():
|
||||
ety := ty.typeImpl.(typeList).ElementTypeT
|
||||
if len(val.v.([]interface{})) == len(other.v.([]interface{})) {
|
||||
if len(val.v.([]any)) == len(other.v.([]any)) {
|
||||
result = true
|
||||
for i := range val.v.([]interface{}) {
|
||||
for i := range val.v.([]any) {
|
||||
lhs := Value{
|
||||
ty: ety,
|
||||
v: val.v.([]interface{})[i],
|
||||
v: val.v.([]any)[i],
|
||||
}
|
||||
rhs := Value{
|
||||
ty: ety,
|
||||
v: other.v.([]interface{})[i],
|
||||
v: other.v.([]any)[i],
|
||||
}
|
||||
eq := lhs.Equals(rhs)
|
||||
if !eq.IsKnown() {
|
||||
@@ -303,8 +304,8 @@ func (val Value) Equals(other Value) Value {
|
||||
}
|
||||
}
|
||||
case ty.IsSetType():
|
||||
s1 := val.v.(set.Set[interface{}])
|
||||
s2 := other.v.(set.Set[interface{}])
|
||||
s1 := val.v.(set.Set[any])
|
||||
s2 := other.v.(set.Set[any])
|
||||
equal := true
|
||||
|
||||
// Two sets are equal if all of their values are known and all values
|
||||
@@ -331,20 +332,20 @@ func (val Value) Equals(other Value) Value {
|
||||
result = equal
|
||||
case ty.IsMapType():
|
||||
ety := ty.typeImpl.(typeMap).ElementTypeT
|
||||
if len(val.v.(map[string]interface{})) == len(other.v.(map[string]interface{})) {
|
||||
if len(val.v.(map[string]any)) == len(other.v.(map[string]any)) {
|
||||
result = true
|
||||
for k := range val.v.(map[string]interface{}) {
|
||||
if _, ok := other.v.(map[string]interface{})[k]; !ok {
|
||||
for k := range val.v.(map[string]any) {
|
||||
if _, ok := other.v.(map[string]any)[k]; !ok {
|
||||
result = false
|
||||
break
|
||||
}
|
||||
lhs := Value{
|
||||
ty: ety,
|
||||
v: val.v.(map[string]interface{})[k],
|
||||
v: val.v.(map[string]any)[k],
|
||||
}
|
||||
rhs := Value{
|
||||
ty: ety,
|
||||
v: other.v.(map[string]interface{})[k],
|
||||
v: other.v.(map[string]any)[k],
|
||||
}
|
||||
eq := lhs.Equals(rhs)
|
||||
if !eq.IsKnown() {
|
||||
@@ -462,11 +463,11 @@ func (val Value) RawEquals(other Value) bool {
|
||||
for attr, aty := range oty.AttrTypes {
|
||||
lhs := Value{
|
||||
ty: aty,
|
||||
v: val.v.(map[string]interface{})[attr],
|
||||
v: val.v.(map[string]any)[attr],
|
||||
}
|
||||
rhs := Value{
|
||||
ty: aty,
|
||||
v: other.v.(map[string]interface{})[attr],
|
||||
v: other.v.(map[string]any)[attr],
|
||||
}
|
||||
eq := lhs.RawEquals(rhs)
|
||||
if !eq {
|
||||
@@ -479,11 +480,11 @@ func (val Value) RawEquals(other Value) bool {
|
||||
for i, ety := range tty.ElemTypes {
|
||||
lhs := Value{
|
||||
ty: ety,
|
||||
v: val.v.([]interface{})[i],
|
||||
v: val.v.([]any)[i],
|
||||
}
|
||||
rhs := Value{
|
||||
ty: ety,
|
||||
v: other.v.([]interface{})[i],
|
||||
v: other.v.([]any)[i],
|
||||
}
|
||||
eq := lhs.RawEquals(rhs)
|
||||
if !eq {
|
||||
@@ -493,15 +494,15 @@ func (val Value) RawEquals(other Value) bool {
|
||||
return true
|
||||
case ty.IsListType():
|
||||
ety := ty.typeImpl.(typeList).ElementTypeT
|
||||
if len(val.v.([]interface{})) == len(other.v.([]interface{})) {
|
||||
for i := range val.v.([]interface{}) {
|
||||
if len(val.v.([]any)) == len(other.v.([]any)) {
|
||||
for i := range val.v.([]any) {
|
||||
lhs := Value{
|
||||
ty: ety,
|
||||
v: val.v.([]interface{})[i],
|
||||
v: val.v.([]any)[i],
|
||||
}
|
||||
rhs := Value{
|
||||
ty: ety,
|
||||
v: other.v.([]interface{})[i],
|
||||
v: other.v.([]any)[i],
|
||||
}
|
||||
eq := lhs.RawEquals(rhs)
|
||||
if !eq {
|
||||
@@ -545,18 +546,18 @@ func (val Value) RawEquals(other Value) bool {
|
||||
}
|
||||
valUn, _ := val.Unmark()
|
||||
otherUn, _ := other.Unmark()
|
||||
if len(valUn.v.(map[string]interface{})) == len(otherUn.v.(map[string]interface{})) {
|
||||
for k := range valUn.v.(map[string]interface{}) {
|
||||
if _, ok := otherUn.v.(map[string]interface{})[k]; !ok {
|
||||
if len(valUn.v.(map[string]any)) == len(otherUn.v.(map[string]any)) {
|
||||
for k := range valUn.v.(map[string]any) {
|
||||
if _, ok := otherUn.v.(map[string]any)[k]; !ok {
|
||||
return false
|
||||
}
|
||||
lhs := Value{
|
||||
ty: ety,
|
||||
v: valUn.v.(map[string]interface{})[k],
|
||||
v: valUn.v.(map[string]any)[k],
|
||||
}
|
||||
rhs := Value{
|
||||
ty: ety,
|
||||
v: otherUn.v.(map[string]interface{})[k],
|
||||
v: otherUn.v.(map[string]any)[k],
|
||||
}
|
||||
eq := lhs.RawEquals(rhs)
|
||||
if !eq {
|
||||
@@ -816,7 +817,7 @@ func (val Value) GetAttr(name string) Value {
|
||||
|
||||
return Value{
|
||||
ty: attrType,
|
||||
v: val.v.(map[string]interface{})[name],
|
||||
v: val.v.(map[string]any)[name],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -872,7 +873,7 @@ func (val Value) Index(key Value) Value {
|
||||
|
||||
return Value{
|
||||
ty: elty,
|
||||
v: val.v.([]interface{})[index],
|
||||
v: val.v.([]any)[index],
|
||||
}
|
||||
case val.Type().IsMapType():
|
||||
elty := val.Type().ElementType()
|
||||
@@ -895,7 +896,7 @@ func (val Value) Index(key Value) Value {
|
||||
|
||||
return Value{
|
||||
ty: elty,
|
||||
v: val.v.(map[string]interface{})[keyStr],
|
||||
v: val.v.(map[string]any)[keyStr],
|
||||
}
|
||||
case val.Type().IsTupleType():
|
||||
if key.Type() == DynamicPseudoType {
|
||||
@@ -922,7 +923,7 @@ func (val Value) Index(key Value) Value {
|
||||
|
||||
return Value{
|
||||
ty: eltys[index],
|
||||
v: val.v.([]interface{})[index],
|
||||
v: val.v.([]any)[index],
|
||||
}
|
||||
default:
|
||||
panic("not a list, map, or tuple type")
|
||||
@@ -969,7 +970,7 @@ func (val Value) HasIndex(key Value) Value {
|
||||
return False
|
||||
}
|
||||
|
||||
return BoolVal(int(index) < len(val.v.([]interface{})) && index >= 0)
|
||||
return BoolVal(int(index) < len(val.v.([]any)) && index >= 0)
|
||||
case val.Type().IsMapType():
|
||||
if key.Type() == DynamicPseudoType {
|
||||
return UnknownVal(Bool).RefineNotNull()
|
||||
@@ -986,7 +987,7 @@ func (val Value) HasIndex(key Value) Value {
|
||||
}
|
||||
|
||||
keyStr := key.v.(string)
|
||||
_, exists := val.v.(map[string]interface{})[keyStr]
|
||||
_, exists := val.v.(map[string]any)[keyStr]
|
||||
|
||||
return BoolVal(exists)
|
||||
case val.Type().IsTupleType():
|
||||
@@ -1062,7 +1063,7 @@ func (val Value) HasElement(elem Value) Value {
|
||||
return False
|
||||
}
|
||||
|
||||
s := val.v.(set.Set[interface{}])
|
||||
s := val.v.(set.Set[any])
|
||||
if !s.Has(elem.v) {
|
||||
return noMatchResult
|
||||
}
|
||||
@@ -1102,7 +1103,7 @@ func (val Value) Length() Value {
|
||||
// may or may not be equal to other elements in the set, and thus they
|
||||
// may or may not coalesce with other elements and produce fewer
|
||||
// items in the resulting set.
|
||||
storeLength := int64(val.v.(set.Set[interface{}]).Length())
|
||||
storeLength := int64(val.v.(set.Set[any]).Length())
|
||||
if storeLength == 1 || val.IsWhollyKnown() {
|
||||
// If our set is wholly known then we know its length.
|
||||
//
|
||||
@@ -1164,7 +1165,7 @@ func (val Value) LengthInt() int {
|
||||
switch {
|
||||
|
||||
case val.ty.IsListType():
|
||||
return len(val.v.([]interface{}))
|
||||
return len(val.v.([]any))
|
||||
|
||||
case val.ty.IsSetType():
|
||||
// NOTE: This is technically not correct in cases where the set
|
||||
@@ -1176,40 +1177,59 @@ func (val Value) LengthInt() int {
|
||||
// compatibility with callers that were relying on LengthInt rather
|
||||
// than calling Length. Instead of panicking when a set contains an
|
||||
// unknown value, LengthInt returns the largest possible length.
|
||||
return val.v.(set.Set[interface{}]).Length()
|
||||
return val.v.(set.Set[any]).Length()
|
||||
|
||||
case val.ty.IsMapType():
|
||||
return len(val.v.(map[string]interface{}))
|
||||
return len(val.v.(map[string]any))
|
||||
|
||||
default:
|
||||
panic("value is not a collection")
|
||||
}
|
||||
}
|
||||
|
||||
// Elements returns an iterable sequence over the elements of the reciever,
|
||||
// which must be a collection type, a tuple type, or an object type.
|
||||
// If called on a value of any other type, this method will panic.
|
||||
// The value must also be known, non-null, and unmarked, or this method will
|
||||
// panic.
|
||||
//
|
||||
// Use [Value.CanIterateElements] to check dynamically if a particular value
|
||||
// can support this method without panicking.
|
||||
//
|
||||
// The two values in each iteration represent a key and a value respectively.
|
||||
//
|
||||
// If the receiver is of list type then the key is guaranteed to be of type
|
||||
// [Number] and the values are of the list's element type.
|
||||
//
|
||||
// The the reciever is of a map type then the key is guaranteed to be of type
|
||||
// [String] and the values are of the map's element type. Elements are
|
||||
// produced in ascending lexicographical order by key.
|
||||
//
|
||||
// If the receiver is of a set type then each element is returned as both the
|
||||
// key and the value, because set member values are their own identity.
|
||||
//
|
||||
// If the reciever is of a tuple type then the key is guaranteed to be of type
|
||||
// [Number] and the and the value types match the corresponding element types.
|
||||
//
|
||||
// If the reciever is of an object type then the key is guaranteed to be of
|
||||
// type [String] and the value types match the corresponding attribute types.
|
||||
func (val Value) Elements() iter.Seq2[Value, Value] {
|
||||
return func(yield func(Value, Value) bool) {
|
||||
for it := val.ElementIterator(); it.Next(); {
|
||||
if !yield(it.Element()) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ElementIterator returns an ElementIterator for iterating the elements
|
||||
// of the receiver, which must be a collection type, a tuple type, or an object
|
||||
// type. If called on a method of any other type, this method will panic.
|
||||
// The value must be known and non-null, or this method will panic.
|
||||
//
|
||||
// The value must be Known and non-Null, or this method will panic.
|
||||
//
|
||||
// If the receiver is of a list type, the returned keys will be of type Number
|
||||
// and the values will be of the list's element type.
|
||||
//
|
||||
// If the receiver is of a map type, the returned keys will be of type String
|
||||
// and the value will be of the map's element type. Elements are passed in
|
||||
// ascending lexicographical order by key.
|
||||
//
|
||||
// If the receiver is of a set type, each element is returned as both the
|
||||
// key and the value, since set members are their own identity.
|
||||
//
|
||||
// If the receiver is of a tuple type, the returned keys will be of type Number
|
||||
// and the value will be of the corresponding element's type.
|
||||
//
|
||||
// If the receiver is of an object type, the returned keys will be of type
|
||||
// String and the value will be of the corresponding attributes's type.
|
||||
//
|
||||
// ElementIterator is an integration method, so it cannot handle Unknown
|
||||
// values. This method will panic if the receiver is Unknown.
|
||||
// The element iterator produces keys and values matching what's described
|
||||
// for [Value.Elements]. New code should prefer to use [Value.Elements].
|
||||
func (val Value) ElementIterator() ElementIterator {
|
||||
val.assertUnmarked()
|
||||
if !val.IsKnown() {
|
||||
@@ -1222,7 +1242,7 @@ func (val Value) ElementIterator() ElementIterator {
|
||||
}
|
||||
|
||||
// CanIterateElements returns true if the receiver can support the
|
||||
// ElementIterator method (and by extension, ForEachElement) without panic.
|
||||
// Elements, ElementIterator, and ForEachElement methods without panic.
|
||||
func (val Value) CanIterateElements() bool {
|
||||
return canElementIterator(val)
|
||||
}
|
||||
@@ -1232,7 +1252,9 @@ func (val Value) CanIterateElements() bool {
|
||||
// will panic.
|
||||
//
|
||||
// ForEachElement uses ElementIterator internally, and so the values passed
|
||||
// to the callback are as described for ElementIterator.
|
||||
// to the callback are as described for [Value.Elements]. New code should
|
||||
// prefer to use [Value.Elements] in a normal for loop instead of using this
|
||||
// method.
|
||||
//
|
||||
// Returns true if the iteration exited early due to the callback function
|
||||
// returning true, or false if the loop ran to completion.
|
||||
@@ -1515,7 +1537,7 @@ func (val Value) AsValueSet() ValueSet {
|
||||
// The result is the same pointer that was passed to CapsuleVal to create
|
||||
// the value. Since cty considers values to be immutable, it is strongly
|
||||
// recommended to treat the encapsulated value itself as immutable too.
|
||||
func (val Value) EncapsulatedValue() interface{} {
|
||||
func (val Value) EncapsulatedValue() any {
|
||||
val.assertUnmarked()
|
||||
if !val.Type().IsCapsuleType() {
|
||||
panic("not a capsule-typed value")
|
||||
|
||||
+28
@@ -1,11 +1,19 @@
|
||||
package cty
|
||||
|
||||
import (
|
||||
"io"
|
||||
"iter"
|
||||
)
|
||||
|
||||
// Walk visits all of the values in a possibly-complex structure, calling
|
||||
// a given function for each value.
|
||||
//
|
||||
// For example, given a list of strings the callback would first be called
|
||||
// with the whole list and then called once for each element of the list.
|
||||
//
|
||||
// New callers may prefer to use [DeepValues] instead, because that returns
|
||||
// a result usable with a normal for loop.
|
||||
//
|
||||
// The callback function may prevent recursive visits to child values by
|
||||
// returning false. The callback function my halt the walk altogether by
|
||||
// returning a non-nil error. If the returned error is about the element
|
||||
@@ -19,6 +27,26 @@ func Walk(val Value, cb func(Path, Value) (bool, error)) error {
|
||||
return walk(path, val, cb)
|
||||
}
|
||||
|
||||
// DeepValues returns an iterable sequence containing at least the given
|
||||
// value but also, if it is of a collection or structural type, the other values
|
||||
// nested within it recursively.
|
||||
//
|
||||
// The [Path] values in different elements of the sequence may share a backing
|
||||
// array to reduce garbage generated during iteration, so if a caller wishes to
|
||||
// preserve a path outside of a single loop iteration the caller must copy it to
|
||||
// a separate Path value with its own separate backing array, such as by calling
|
||||
// [Path.Copy].
|
||||
func DeepValues(val Value) iter.Seq2[Path, Value] {
|
||||
return func(yield func(Path, Value) bool) {
|
||||
Walk(val, func(p Path, v Value) (bool, error) {
|
||||
if !yield(p, v) {
|
||||
return false, io.EOF // arbitrary error just to get Walk to stop
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func walk(path Path, val Value, cb func(Path, Value) (bool, error)) error {
|
||||
deeper, err := cb(path, val)
|
||||
if err != nil {
|
||||
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.24
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/mlkem"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"slices"
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
const (
|
||||
kexAlgoMLKEM768xCurve25519SHA256 = "mlkem768x25519-sha256"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// After Go 1.24rc1 mlkem swapped the order of return values of Encapsulate.
|
||||
// See #70950.
|
||||
if runtime.Version() == "go1.24rc1" {
|
||||
return
|
||||
}
|
||||
supportedKexAlgos = slices.Insert(supportedKexAlgos, 0, kexAlgoMLKEM768xCurve25519SHA256)
|
||||
preferredKexAlgos = slices.Insert(preferredKexAlgos, 0, kexAlgoMLKEM768xCurve25519SHA256)
|
||||
kexAlgoMap[kexAlgoMLKEM768xCurve25519SHA256] = &mlkem768WithCurve25519sha256{}
|
||||
}
|
||||
|
||||
// mlkem768WithCurve25519sha256 implements the hybrid ML-KEM768 with
|
||||
// curve25519-sha256 key exchange method, as described by
|
||||
// draft-kampanakis-curdle-ssh-pq-ke-05 section 2.3.3.
|
||||
type mlkem768WithCurve25519sha256 struct{}
|
||||
|
||||
func (kex *mlkem768WithCurve25519sha256) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) {
|
||||
var c25519kp curve25519KeyPair
|
||||
if err := c25519kp.generate(rand); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
seed := make([]byte, mlkem.SeedSize)
|
||||
if _, err := io.ReadFull(rand, seed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mlkemDk, err := mlkem.NewDecapsulationKey768(seed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hybridKey := append(mlkemDk.EncapsulationKey().Bytes(), c25519kp.pub[:]...)
|
||||
if err := c.writePacket(Marshal(&kexECDHInitMsg{hybridKey})); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reply kexECDHReplyMsg
|
||||
if err = Unmarshal(packet, &reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(reply.EphemeralPubKey) != mlkem.CiphertextSize768+32 {
|
||||
return nil, errors.New("ssh: peer's mlkem768x25519 public value has wrong length")
|
||||
}
|
||||
|
||||
// Perform KEM decapsulate operation to obtain shared key from ML-KEM.
|
||||
mlkem768Secret, err := mlkemDk.Decapsulate(reply.EphemeralPubKey[:mlkem.CiphertextSize768])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Complete Curve25519 ECDH to obtain its shared key.
|
||||
c25519Secret, err := curve25519.X25519(c25519kp.priv[:], reply.EphemeralPubKey[mlkem.CiphertextSize768:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssh: peer's mlkem768x25519 public value is not valid: %w", err)
|
||||
}
|
||||
// Compute actual shared key.
|
||||
h := sha256.New()
|
||||
h.Write(mlkem768Secret)
|
||||
h.Write(c25519Secret)
|
||||
secret := h.Sum(nil)
|
||||
|
||||
h.Reset()
|
||||
magics.write(h)
|
||||
writeString(h, reply.HostKey)
|
||||
writeString(h, hybridKey)
|
||||
writeString(h, reply.EphemeralPubKey)
|
||||
|
||||
K := make([]byte, stringLength(len(secret)))
|
||||
marshalString(K, secret)
|
||||
h.Write(K)
|
||||
|
||||
return &kexResult{
|
||||
H: h.Sum(nil),
|
||||
K: K,
|
||||
HostKey: reply.HostKey,
|
||||
Signature: reply.Signature,
|
||||
Hash: crypto.SHA256,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (kex *mlkem768WithCurve25519sha256) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv AlgorithmSigner, algo string) (*kexResult, error) {
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kexInit kexECDHInitMsg
|
||||
if err = Unmarshal(packet, &kexInit); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(kexInit.ClientPubKey) != mlkem.EncapsulationKeySize768+32 {
|
||||
return nil, errors.New("ssh: peer's ML-KEM768/curve25519 public value has wrong length")
|
||||
}
|
||||
|
||||
encapsulationKey, err := mlkem.NewEncapsulationKey768(kexInit.ClientPubKey[:mlkem.EncapsulationKeySize768])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssh: peer's ML-KEM768 encapsulation key is not valid: %w", err)
|
||||
}
|
||||
// Perform KEM encapsulate operation to obtain ciphertext and shared key.
|
||||
mlkem768Secret, mlkem768Ciphertext := encapsulationKey.Encapsulate()
|
||||
|
||||
// Perform server side of Curve25519 ECDH to obtain server public value and
|
||||
// shared key.
|
||||
var c25519kp curve25519KeyPair
|
||||
if err := c25519kp.generate(rand); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c25519Secret, err := curve25519.X25519(c25519kp.priv[:], kexInit.ClientPubKey[mlkem.EncapsulationKeySize768:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssh: peer's ML-KEM768/curve25519 public value is not valid: %w", err)
|
||||
}
|
||||
hybridKey := append(mlkem768Ciphertext, c25519kp.pub[:]...)
|
||||
|
||||
// Compute actual shared key.
|
||||
h := sha256.New()
|
||||
h.Write(mlkem768Secret)
|
||||
h.Write(c25519Secret)
|
||||
secret := h.Sum(nil)
|
||||
|
||||
hostKeyBytes := priv.PublicKey().Marshal()
|
||||
|
||||
h.Reset()
|
||||
magics.write(h)
|
||||
writeString(h, hostKeyBytes)
|
||||
writeString(h, kexInit.ClientPubKey)
|
||||
writeString(h, hybridKey)
|
||||
|
||||
K := make([]byte, stringLength(len(secret)))
|
||||
marshalString(K, secret)
|
||||
h.Write(K)
|
||||
|
||||
H := h.Sum(nil)
|
||||
|
||||
sig, err := signAndMarshal(priv, rand, H, algo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reply := kexECDHReplyMsg{
|
||||
EphemeralPubKey: hybridKey,
|
||||
HostKey: hostKeyBytes,
|
||||
Signature: sig,
|
||||
}
|
||||
if err := c.writePacket(Marshal(&reply)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &kexResult{
|
||||
H: H,
|
||||
K: K,
|
||||
HostKey: hostKeyBytes,
|
||||
Signature: sig,
|
||||
Hash: crypto.SHA256,
|
||||
}, nil
|
||||
}
|
||||
+58
-10
@@ -6,6 +6,7 @@ package term
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@@ -36,6 +37,26 @@ var vt100EscapeCodes = EscapeCodes{
|
||||
Reset: []byte{keyEscape, '[', '0', 'm'},
|
||||
}
|
||||
|
||||
// A History provides a (possibly bounded) queue of input lines read by [Terminal.ReadLine].
|
||||
type History interface {
|
||||
// Add will be called by [Terminal.ReadLine] to add
|
||||
// a new, most recent entry to the history.
|
||||
// It is allowed to drop any entry, including
|
||||
// the entry being added (e.g., if it's deemed an invalid entry),
|
||||
// the least-recent entry (e.g., to keep the history bounded),
|
||||
// or any other entry.
|
||||
Add(entry string)
|
||||
|
||||
// Len returns the number of entries in the history.
|
||||
Len() int
|
||||
|
||||
// At returns an entry from the history.
|
||||
// Index 0 is the most-recently added entry and
|
||||
// index Len()-1 is the least-recently added entry.
|
||||
// If index is < 0 or >= Len(), it panics.
|
||||
At(idx int) string
|
||||
}
|
||||
|
||||
// Terminal contains the state for running a VT100 terminal that is capable of
|
||||
// reading lines of input.
|
||||
type Terminal struct {
|
||||
@@ -86,9 +107,14 @@ type Terminal struct {
|
||||
remainder []byte
|
||||
inBuf [256]byte
|
||||
|
||||
// history contains previously entered commands so that they can be
|
||||
// accessed with the up and down keys.
|
||||
history stRingBuffer
|
||||
// History records and retrieves lines of input read by [ReadLine] which
|
||||
// a user can retrieve and navigate using the up and down arrow keys.
|
||||
//
|
||||
// It is not safe to call ReadLine concurrently with any methods on History.
|
||||
//
|
||||
// [NewTerminal] sets this to a default implementation that records the
|
||||
// last 100 lines of input.
|
||||
History History
|
||||
// historyIndex stores the currently accessed history entry, where zero
|
||||
// means the immediately previous entry.
|
||||
historyIndex int
|
||||
@@ -111,6 +137,7 @@ func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
|
||||
termHeight: 24,
|
||||
echo: true,
|
||||
historyIndex: -1,
|
||||
History: &stRingBuffer{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,6 +477,23 @@ func visualLength(runes []rune) int {
|
||||
return length
|
||||
}
|
||||
|
||||
// histroryAt unlocks the terminal and relocks it while calling History.At.
|
||||
func (t *Terminal) historyAt(idx int) (string, bool) {
|
||||
t.lock.Unlock() // Unlock to avoid deadlock if History methods use the output writer.
|
||||
defer t.lock.Lock() // panic in At (or Len) protection.
|
||||
if idx < 0 || idx >= t.History.Len() {
|
||||
return "", false
|
||||
}
|
||||
return t.History.At(idx), true
|
||||
}
|
||||
|
||||
// historyAdd unlocks the terminal and relocks it while calling History.Add.
|
||||
func (t *Terminal) historyAdd(entry string) {
|
||||
t.lock.Unlock() // Unlock to avoid deadlock if History methods use the output writer.
|
||||
defer t.lock.Lock() // panic in Add protection.
|
||||
t.History.Add(entry)
|
||||
}
|
||||
|
||||
// handleKey processes the given key and, optionally, returns a line of text
|
||||
// that the user has entered.
|
||||
func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
||||
@@ -497,7 +541,7 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
||||
t.pos = len(t.line)
|
||||
t.moveCursorToPos(t.pos)
|
||||
case keyUp:
|
||||
entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1)
|
||||
entry, ok := t.historyAt(t.historyIndex + 1)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
@@ -516,7 +560,7 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
||||
t.setLine(runes, len(runes))
|
||||
t.historyIndex--
|
||||
default:
|
||||
entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1)
|
||||
entry, ok := t.historyAt(t.historyIndex - 1)
|
||||
if ok {
|
||||
t.historyIndex--
|
||||
runes := []rune(entry)
|
||||
@@ -781,7 +825,7 @@ func (t *Terminal) readLine() (line string, err error) {
|
||||
if lineOk {
|
||||
if t.echo {
|
||||
t.historyIndex = -1
|
||||
t.history.Add(line)
|
||||
t.historyAdd(line)
|
||||
}
|
||||
if lineIsPasted {
|
||||
err = ErrPasteIndicator
|
||||
@@ -938,19 +982,23 @@ func (s *stRingBuffer) Add(a string) {
|
||||
}
|
||||
}
|
||||
|
||||
// NthPreviousEntry returns the value passed to the nth previous call to Add.
|
||||
func (s *stRingBuffer) Len() int {
|
||||
return s.size
|
||||
}
|
||||
|
||||
// At returns the value passed to the nth previous call to Add.
|
||||
// If n is zero then the immediately prior value is returned, if one, then the
|
||||
// next most recent, and so on. If such an element doesn't exist then ok is
|
||||
// false.
|
||||
func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) {
|
||||
func (s *stRingBuffer) At(n int) string {
|
||||
if n < 0 || n >= s.size {
|
||||
return "", false
|
||||
panic(fmt.Sprintf("term: history index [%d] out of range [0,%d)", n, s.size))
|
||||
}
|
||||
index := s.head - n
|
||||
if index < 0 {
|
||||
index += s.max
|
||||
}
|
||||
return s.entries[index], true
|
||||
return s.entries[index]
|
||||
}
|
||||
|
||||
// readPasswordLine reads from reader until it finds \n or io.EOF.
|
||||
|
||||
Vendored
+12
-11
@@ -385,8 +385,8 @@ github.com/hashicorp/errwrap
|
||||
# github.com/hashicorp/go-cleanhttp v0.5.2
|
||||
## explicit; go 1.13
|
||||
github.com/hashicorp/go-cleanhttp
|
||||
# github.com/hashicorp/go-cty-funcs v0.0.0-20250210171435-dda779884a9f
|
||||
## explicit; go 1.23
|
||||
# github.com/hashicorp/go-cty-funcs v0.0.0-20250818135842-6aab67130928
|
||||
## explicit; go 1.24
|
||||
github.com/hashicorp/go-cty-funcs/cidr
|
||||
github.com/hashicorp/go-cty-funcs/crypto
|
||||
github.com/hashicorp/go-cty-funcs/encoding
|
||||
@@ -394,8 +394,8 @@ github.com/hashicorp/go-cty-funcs/uuid
|
||||
# github.com/hashicorp/go-multierror v1.1.1
|
||||
## explicit; go 1.13
|
||||
github.com/hashicorp/go-multierror
|
||||
# github.com/hashicorp/hcl/v2 v2.23.0
|
||||
## explicit; go 1.18
|
||||
# github.com/hashicorp/hcl/v2 v2.24.0
|
||||
## explicit; go 1.23.0
|
||||
github.com/hashicorp/hcl/v2
|
||||
github.com/hashicorp/hcl/v2/ext/customdecode
|
||||
github.com/hashicorp/hcl/v2/ext/tryfunc
|
||||
@@ -442,8 +442,8 @@ github.com/mattn/go-runewidth
|
||||
# github.com/mattn/go-shellwords v1.0.12
|
||||
## explicit; go 1.13
|
||||
github.com/mattn/go-shellwords
|
||||
# github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7
|
||||
## explicit
|
||||
# github.com/mitchellh/go-wordwrap v1.0.1
|
||||
## explicit; go 1.14
|
||||
github.com/mitchellh/go-wordwrap
|
||||
# github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
## explicit; go 1.14
|
||||
@@ -691,10 +691,11 @@ github.com/x448/float16
|
||||
# github.com/xhit/go-str2duration/v2 v2.1.0
|
||||
## explicit; go 1.13
|
||||
github.com/xhit/go-str2duration/v2
|
||||
# github.com/zclconf/go-cty v1.16.2
|
||||
## explicit; go 1.18
|
||||
# github.com/zclconf/go-cty v1.17.0
|
||||
## explicit; go 1.23
|
||||
github.com/zclconf/go-cty/cty
|
||||
github.com/zclconf/go-cty/cty/convert
|
||||
github.com/zclconf/go-cty/cty/ctymarks
|
||||
github.com/zclconf/go-cty/cty/ctystrings
|
||||
github.com/zclconf/go-cty/cty/function
|
||||
github.com/zclconf/go-cty/cty/function/stdlib
|
||||
@@ -811,7 +812,7 @@ go.opentelemetry.io/proto/otlp/trace/v1
|
||||
# go.yaml.in/yaml/v3 v3.0.4
|
||||
## explicit; go 1.16
|
||||
go.yaml.in/yaml/v3
|
||||
# golang.org/x/crypto v0.37.0
|
||||
# golang.org/x/crypto v0.38.0
|
||||
## explicit; go 1.23.0
|
||||
golang.org/x/crypto/bcrypt
|
||||
golang.org/x/crypto/blowfish
|
||||
@@ -857,10 +858,10 @@ golang.org/x/sys/plan9
|
||||
golang.org/x/sys/unix
|
||||
golang.org/x/sys/windows
|
||||
golang.org/x/sys/windows/registry
|
||||
# golang.org/x/term v0.31.0
|
||||
# golang.org/x/term v0.32.0
|
||||
## explicit; go 1.23.0
|
||||
golang.org/x/term
|
||||
# golang.org/x/text v0.24.0
|
||||
# golang.org/x/text v0.25.0
|
||||
## explicit; go 1.23.0
|
||||
golang.org/x/text/cases
|
||||
golang.org/x/text/feature/plural
|
||||
|
||||
Reference in New Issue
Block a user