vendor: update hcl dependencies

Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2025-09-10 10:00:58 +02:00
parent 3922da8029
commit f5665d2e42
62 changed files with 1408 additions and 369 deletions
+7 -7
View File
@@ -24,9 +24,9 @@ require (
github.com/google/go-dap v0.12.0 github.com/google/go-dap v0.12.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/google/uuid v1.6.0 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/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/in-toto/in-toto-golang v0.9.0
github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/moby/buildkit v0.24.0 github.com/moby/buildkit v0.24.0
@@ -48,7 +48,7 @@ require (
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250408171107-3dd17559e117 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/contrib/instrumentation/net/http/otelhttp v0.60.0
go.opentelemetry.io/otel v1.35.0 go.opentelemetry.io/otel v1.35.0
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.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/mod v0.24.0
golang.org/x/sync v0.16.0 golang.org/x/sync v0.16.0
golang.org/x/sys v0.33.0 golang.org/x/sys v0.33.0
golang.org/x/term v0.31.0 golang.org/x/term v0.32.0
golang.org/x/text v0.24.0 golang.org/x/text v0.25.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a
google.golang.org/grpc v1.72.2 google.golang.org/grpc v1.72.2
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 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/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-shellwords v1.0.12 // 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/docker-image-spec v1.3.1 // indirect
github.com/moby/locker v1.0.1 // indirect github.com/moby/locker v1.0.1 // indirect
github.com/moby/patternmatcher v0.6.0 // 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/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.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/net v0.39.0 // indirect
golang.org/x/oauth2 v0.29.0 // indirect golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/time v0.11.0 // indirect golang.org/x/time v0.11.0 // indirect
+14 -14
View File
@@ -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/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 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 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-20250818135842-6aab67130928 h1:NFpfJOqEV8DUhomtXx9A0FfcMg7OCYRugdyw6Ar/Y7s=
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/go.mod h1:YC9ASYt9Z9sQEAtzCe+yaAzi3E7wcxfRphDXtwZoWC0=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 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/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.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= 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/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 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU=
github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= 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.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 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= 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 v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 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 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= 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= 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/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.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/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.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0=
github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= 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 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 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-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-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.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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.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= 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 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 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 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 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= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+1
View File
@@ -0,0 +1 @@
1.23
+18
View File
@@ -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
View File
@@ -1,5 +1,23 @@
# HCL Changelog # 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) ## v2.22.0 (August 26, 2024)
### Enhancements ### Enhancements
+4 -4
View File
@@ -106,10 +106,10 @@ func (d *Diagnostic) Error() string {
// APIs that normally deal in vanilla Go errors. // APIs that normally deal in vanilla Go errors.
func (d Diagnostics) Error() string { func (d Diagnostics) Error() string {
count := len(d) count := len(d)
switch { switch count {
case count == 0: case 0:
return "no diagnostics" return "no diagnostics"
case count == 1: case 1:
return d[0].Error() return d[0].Error()
default: default:
return fmt.Sprintf("%s, and %d other diagnostic(s)", d[0].Error(), count-1) 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 // This is provided as a convenience for returning from a function that
// collects and then returns a set of diagnostics: // 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 // Note that this modifies the array underlying the diagnostics slice, so
// must be used carefully within a single codepath. It is incorrect (and rude) // must be used carefully within a single codepath. It is incorrect (and rude)
+43 -12
View File
@@ -71,7 +71,10 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
severityStr = "???????" 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 { if diag.Subject != nil {
snipRange := *diag.Subject snipRange := *diag.Subject
@@ -97,7 +100,10 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
file := w.files[diag.Subject.Filename] file := w.files[diag.Subject.Filename]
if file == nil || file.Bytes == nil { 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 { } else {
var contextLine string 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 src := file.Bytes
sc := NewRangeScanner(src, diag.Subject.Filename, bufio.ScanLines) 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) beforeRange, highlightedRange, afterRange := lineRange.PartitionAround(highlightRange)
if highlightedRange.Empty() { 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 { } else {
before := beforeRange.SliceBytes(src) before := beforeRange.SliceBytes(src)
highlighted := highlightedRange.SliceBytes(src) highlighted := highlightedRange.SliceBytes(src)
after := afterRange.SliceBytes(src) after := afterRange.SliceBytes(src)
fmt.Fprintf( _, err = fmt.Fprintf(
w.wr, "%4d: %s%s%s%s%s\n", w.wr, "%4d: %s%s%s%s%s\n",
lineRange.Start.Line, lineRange.Start.Line,
before, before,
highlightCode, highlighted, resetCode, highlightCode, highlighted, resetCode,
after, 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 { if diag.Expression != nil && diag.EvalContext != nil {
@@ -182,16 +200,26 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
for i, stmt := range stmts { for i, stmt := range stmts {
switch i { switch i {
case 0: case 0:
w.wr.Write([]byte{'w', 'i', 't', 'h', ' '}) _, err = w.wr.Write([]byte{'w', 'i', 't', 'h', ' '})
default: 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 { switch i {
case last: case last:
w.wr.Write([]byte{'.', '\n', '\n'}) _, err = w.wr.Write([]byte{'.', '\n', '\n'})
default: 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 { if w.width != 0 {
detail = wordwrap.WrapString(detail, w.width) 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 return nil
+16 -16
View File
@@ -9,25 +9,25 @@
// configurations in either native HCL syntax or JSON syntax into a Go struct // configurations in either native HCL syntax or JSON syntax into a Go struct
// type: // type:
// //
// package main // package main
// //
// import ( // import (
// "log" // "log"
// "github.com/hashicorp/hcl/v2/hclsimple" // "github.com/hashicorp/hcl/v2/hclsimple"
// ) // )
// //
// type Config struct { // type Config struct {
// LogLevel string `hcl:"log_level"` // LogLevel string `hcl:"log_level"`
// } // }
// //
// func main() { // func main() {
// var config Config // var config Config
// err := hclsimple.DecodeFile("config.hcl", nil, &config) // err := hclsimple.DecodeFile("config.hcl", nil, &config)
// if err != nil { // if err != nil {
// log.Fatalf("Failed to load configuration: %s", err) // log.Fatalf("Failed to load configuration: %s", err)
// } // }
// log.Printf("Configuration is %#v", config) // log.Printf("Configuration is %#v", config)
// } // }
// //
// If your application needs more control over the evaluation of the // If your application needs more control over the evaluation of the
// configuration, you can use the functions in the subdirectories hclparse, // configuration, you can use the functions in the subdirectories hclparse,
+3 -3
View File
@@ -8,7 +8,7 @@
// use any of the usual language operators. This is similar to type expressions // use any of the usual language operators. This is similar to type expressions
// in statically-typed programming languages. // in statically-typed programming languages.
// //
// variable "example" { // variable "example" {
// type = list(string) // type = list(string)
// } // }
package typeexpr package typeexpr
+2 -1
View File
@@ -46,7 +46,8 @@ func TypeConstraintFromVal(v cty.Value) cty.Type {
// ConvertFunc is a cty function that implements type conversions. // ConvertFunc is a cty function that implements type conversions.
// //
// Its signature is as follows: // Its signature is as follows:
// convert(value, type_constraint) //
// convert(value, type_constraint)
// //
// ...where type_constraint is a type constraint expression as defined by // ...where type_constraint is a type constraint expression as defined by
// typeexpr.TypeConstraint. // typeexpr.TypeConstraint.
+33 -8
View File
@@ -9,9 +9,10 @@ import (
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty/convert" "github.com/zclconf/go-cty/cty/convert"
"github.com/zclconf/go-cty/cty/gocty" "github.com/zclconf/go-cty/cty/gocty"
"github.com/hashicorp/hcl/v2"
) )
// DecodeBody extracts the configuration within the given body into the given // 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 // 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 // so the caller can deal with it within the cty realm rather
// than within the Go realm. // than within the Go realm.
synthExpr := hcl.StaticExpr(cty.NullVal(cty.DynamicPseudoType), body.MissingItemRange()) 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 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 { switch {
case attrType.AssignableTo(field.Type): case attrType.AssignableTo(field.Type):
fieldV.Set(reflect.ValueOf(attr)) fieldV.Set(reflect.ValueOf(attr))
@@ -182,7 +195,7 @@ func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value)
case isSlice: case isSlice:
elemType := ty elemType := ty
if isPtr { if isPtr {
elemType = reflect.PtrTo(ty) elemType = reflect.PointerTo(ty)
} }
sli := val.Field(fieldIdx) sli := val.Field(fieldIdx)
if sli.IsNil() { 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 { func decodeBlockToValue(block *hcl.Block, ctx *hcl.EvalContext, v reflect.Value) hcl.Diagnostics {
diags := decodeBodyToValue(block.Body, ctx, v) diags := decodeBodyToValue(block.Body, ctx, v)
if len(block.Labels) > 0 { blockTags := getFieldTags(v.Type())
blockTags := getFieldTags(v.Type()) for li, lv := range block.Labels {
for li, lv := range block.Labels { lfieldIdx := blockTags.Labels[li].FieldIndex
lfieldIdx := blockTags.Labels[li].FieldIndex lfieldName := blockTags.Labels[li].Name
v.Field(lfieldIdx).Set(reflect.ValueOf(lv))
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 return diags
} }
+38 -8
View File
@@ -10,18 +10,18 @@
// A struct field tag scheme is used, similar to other decoding and // A struct field tag scheme is used, similar to other decoding and
// unmarshalling libraries. The tags are formatted as in the following example: // 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 // Within each tag there are two comma-separated tokens. The first is the
// name of the corresponding construct in configuration, while the second // name of the corresponding construct in configuration, while the second
// is a keyword giving the kind of construct expected. The following // is a keyword giving the kind of construct expected. The following
// kind keywords are supported: // kind keywords are supported:
// //
// attr (the default) indicates that the value is to be populated from an attribute // 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 // block indicates that the value is to populated from a block
// label indicates that the value is to populated from a block label // label indicates that the value is to populated from a block label
// optional is the same as attr, but the field is optional // 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 // 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 // "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 // 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 // "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 // 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 // the blocks being decoded. In this case, the name token is used (a) as
// an identifier for the label in diagnostic messages. // 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 // "optional" fields behave like "attr" fields, but they are optional
// and will not give parsing errors if they are missing. // 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 // present then any attributes or blocks not matched by another valid tag
// will cause an error diagnostic. // 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 // Only a subset of this tagging/typing vocabulary is supported for the
// "Encode" family of functions. See the EncodeIntoBody docs for full details // "Encode" family of functions. See the EncodeIntoBody docs for full details
// on the constraints there. // on the constraints there.
+36 -3
View File
@@ -118,18 +118,31 @@ type fieldTags struct {
Remain *int Remain *int
Body *int Body *int
Optional map[string]bool 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 { type labelField struct {
FieldIndex int FieldIndex int
RangeIndex int
Name string Name string
} }
func getFieldTags(ty reflect.Type) *fieldTags { func getFieldTags(ty reflect.Type) *fieldTags {
ret := &fieldTags{ ret := &fieldTags{
Attributes: map[string]int{}, Attributes: map[string]int{},
Blocks: map[string]int{}, Blocks: map[string]int{},
Optional: map[string]bool{}, Optional: map[string]bool{},
AttributeRange: map[string]int{},
AttributeNameRange: map[string]int{},
AttributeValueRange: map[string]int{},
LabelRange: map[string]int{},
} }
ct := ty.NumField() ct := ty.NumField()
@@ -175,6 +188,26 @@ func getFieldTags(ty reflect.Type) *fieldTags {
case "optional": case "optional":
ret.Attributes[name] = i ret.Attributes[name] = i
ret.Optional[name] = true 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: default:
panic(fmt.Sprintf("invalid hcl field tag kind %q on %s %q", kind, field.Type.String(), field.Name)) panic(fmt.Sprintf("invalid hcl field tag kind %q on %s %q", kind, field.Type.String(), field.Name))
} }
+1 -1
View File
@@ -14,6 +14,6 @@ var victimBody hcl.Body
var exprType = reflect.TypeOf(&victimExpr).Elem() var exprType = reflect.TypeOf(&victimExpr).Elem()
var bodyType = reflect.TypeOf(&victimBody).Elem() var bodyType = reflect.TypeOf(&victimBody).Elem()
var blockType = reflect.TypeOf((*hcl.Block)(nil))
var attrType = reflect.TypeOf((*hcl.Attribute)(nil)) var attrType = reflect.TypeOf((*hcl.Attribute)(nil))
var attrsType = reflect.TypeOf(hcl.Attributes(nil)) var attrsType = reflect.TypeOf(hcl.Attributes(nil))
+2 -2
View File
@@ -17,7 +17,7 @@ package hclparse
import ( import (
"fmt" "fmt"
"io/ioutil" "os"
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax" "github.com/hashicorp/hcl/v2/hclsyntax"
@@ -70,7 +70,7 @@ func (p *Parser) ParseHCLFile(filename string) (*hcl.File, hcl.Diagnostics) {
return existing, nil return existing, nil
} }
src, err := ioutil.ReadFile(filename) src, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, hcl.Diagnostics{ return nil, hcl.Diagnostics{
{ {
+17
View File
@@ -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 // 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 // the value will be treated as an implicit single-item tuple, or as
// an empty tuple if the value is null. // 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()) autoUpgrade := !(sourceTy.IsTupleType() || sourceTy.IsListType() || sourceTy.IsSetType())
if sourceVal.IsNull() { if sourceVal.IsNull() {
@@ -1938,6 +1939,22 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
diags = append(diags, tyDiags...) diags = append(diags, tyDiags...)
return cty.ListValEmpty(ty.ElementType()).WithMarks(marks), diags 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 return cty.ListVal(vals).WithMarks(marks), diags
default: default:
return cty.TupleVal(vals).WithMarks(marks), diags return cty.TupleVal(vals).WithMarks(marks), diags
+101 -7
View File
@@ -16,16 +16,86 @@ import (
type Operation struct { type Operation struct {
Impl function.Function Impl function.Function
Type cty.Type 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 ( var (
OpLogicalOr = &Operation{ OpLogicalOr = &Operation{
Impl: stdlib.OrFunc, Impl: stdlib.OrFunc,
Type: cty.Bool, 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{ OpLogicalAnd = &Operation{
Impl: stdlib.AndFunc, Impl: stdlib.AndFunc,
Type: cty.Bool, 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{ OpLogicalNot = &Operation{
Impl: stdlib.NotFunc, Impl: stdlib.NotFunc,
@@ -145,10 +215,6 @@ func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
var diags hcl.Diagnostics var diags hcl.Diagnostics
givenLHSVal, lhsDiags := e.LHS.Value(ctx) 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) lhsVal, err := convert.Convert(givenLHSVal, lhsParam.Type)
if err != nil { if err != nil {
diags = append(diags, &hcl.Diagnostic{ diags = append(diags, &hcl.Diagnostic{
@@ -161,6 +227,8 @@ func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
EvalContext: ctx, EvalContext: ctx,
}) })
} }
givenRHSVal, rhsDiags := e.RHS.Value(ctx)
rhsVal, err := convert.Convert(givenRHSVal, rhsParam.Type) rhsVal, err := convert.Convert(givenRHSVal, rhsParam.Type)
if err != nil { if err != nil {
diags = append(diags, &hcl.Diagnostic{ 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() { if diags.HasErrors() {
// Don't actually try the call if we have errors already, since the // Add the rest of the diagnostic in case that helps the user, but keep
// this will probably just produce a confusing duplicative diagnostic. // them separate as we continue for short-circuit handling.
diags = append(diags, lhsDiags...)
diags = append(diags, rhsDiags...)
return cty.UnknownVal(e.Op.Type), diags 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} args := []cty.Value{lhsVal, rhsVal}
result, err := impl.Call(args) result, err := impl.Call(args)
if err != nil { 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 cty.UnknownVal(e.Op.Type), diags
} }
return result, diags return result.WithMarks(lhsMarks, rhsMarks), diags
} }
func (e *BinaryOpExpr) Range() hcl.Range { func (e *BinaryOpExpr) Range() hcl.Range {
+3 -5
View File
@@ -184,11 +184,9 @@ func (e *TemplateJoinExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
if val.IsNull() { if val.IsNull() {
diags = append(diags, &hcl.Diagnostic{ diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError, Severity: hcl.DiagError,
Summary: "Invalid template interpolation value", Summary: "Invalid template interpolation value",
Detail: fmt.Sprintf( Detail: "An iteration result is null. Cannot include a null value in a string template.",
"An iteration result is null. Cannot include a null value in a string template.",
),
Subject: e.Range().Ptr(), Subject: e.Range().Ptr(),
Expression: e, Expression: e,
EvalContext: ctx, EvalContext: ctx,
+1 -1
View File
@@ -42,7 +42,7 @@ type Body struct {
} }
// Assert that *Body implements hcl.Body // Assert that *Body implements hcl.Body
var assertBodyImplBody hcl.Body = &Body{} var _ hcl.Body = &Body{}
func (b *Body) walkChildNodes(w internalWalkFunc) { func (b *Body) walkChildNodes(w internalWalkFunc) {
w(b.Attributes) w(b.Attributes)
+1
View File
@@ -20,6 +20,7 @@ func Variables(expr Expression) []hcl.Traversal {
}, },
} }
//nolint:errcheck // FIXME: Propogate diagnostics/errors upward.
Walk(expr, walker) Walk(expr, walker)
return vars return vars
+4 -7
View File
@@ -37,7 +37,7 @@ func (f *File) Body() *Body {
// The tokens first have a simple formatting pass applied that adjusts only // The tokens first have a simple formatting pass applied that adjusts only
// the spaces between them. // the spaces between them.
func (f *File) WriteTo(wr io.Writer) (int64, error) { func (f *File) WriteTo(wr io.Writer) (int64, error) {
tokens := f.inTree.children.BuildTokens(nil) tokens := f.children.BuildTokens(nil)
format(tokens) format(tokens)
return tokens.WriteTo(wr) 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. // the AST API, these will be reflected in the result.
func (f *File) Bytes() []byte { func (f *File) Bytes() []byte {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
//nolint:errcheck // FIXME: Propogate errors upward.
f.WriteTo(buf) f.WriteTo(buf)
return buf.Bytes() return buf.Bytes()
} }
@@ -54,7 +55,6 @@ func (f *File) Bytes() []byte {
type comments struct { type comments struct {
leafNode leafNode
parent *node
tokens Tokens tokens Tokens
} }
@@ -71,8 +71,7 @@ func (c *comments) BuildTokens(to Tokens) Tokens {
type identifier struct { type identifier struct {
leafNode leafNode
parent *node token *Token
token *Token
} }
func newIdentifier(token *Token) *identifier { func newIdentifier(token *Token) *identifier {
@@ -92,8 +91,7 @@ func (i *identifier) hasName(name string) bool {
type number struct { type number struct {
leafNode leafNode
parent *node token *Token
token *Token
} }
func newNumber(token *Token) *number { func newNumber(token *Token) *number {
@@ -109,7 +107,6 @@ func (n *number) BuildTokens(to Tokens) Tokens {
type quoted struct { type quoted struct {
leafNode leafNode
parent *node
tokens Tokens tokens Tokens
} }
+6
View File
@@ -49,3 +49,9 @@ func (a *Attribute) init(name string, expr *Expression) {
func (a *Attribute) Expr() *Expression { func (a *Attribute) Expr() *Expression {
return a.expr.content.(*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
View File
@@ -122,7 +122,7 @@ func newBlockLabels(labels []string) *blockLabels {
} }
func (bl *blockLabels) Replace(newLabels []string) { func (bl *blockLabels) Replace(newLabels []string) {
bl.inTree.children.Clear() bl.children.Clear()
bl.items.Clear() bl.items.Clear()
for _, label := range newLabels { for _, label := range newLabels {
+16 -1
View File
@@ -43,7 +43,7 @@ func (b *Body) Clear() {
} }
func (b *Body) AppendUnstructuredTokens(ts Tokens) { 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 // 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 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 // 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 // given name and labels or returns nil if there is currently no matching
// block. // block.
+3 -2
View File
@@ -429,9 +429,10 @@ func linesForFormat(tokens Tokens) []formatLine {
} }
func tokenIsNewline(tok *Token) bool { func tokenIsNewline(tok *Token) bool {
if tok.Type == hclsyntax.TokenNewline { switch tok.Type {
case hclsyntax.TokenNewline:
return true return true
} else if tok.Type == hclsyntax.TokenComment { case hclsyntax.TokenComment:
// Single line tokens (# and //) consume their terminating newline, // Single line tokens (# and //) consume their terminating newline,
// so we need to treat them as newline tokens as well. // so we need to treat them as newline tokens as well.
if len(tok.Bytes) > 0 && tok.Bytes[len(tok.Bytes)-1] == '\n' { if len(tok.Bytes) > 0 && tok.Bytes[len(tok.Bytes)-1] == '\n' {
+12 -12
View File
@@ -61,7 +61,7 @@ func parse(src []byte, filename string, start hcl.Pos) (*File, hcl.Diagnostics)
body: root, body: root,
} }
nodes := ret.inTree.children nodes := ret.children
nodes.Append(before.Tokens()) nodes.Append(before.Tokens())
nodes.AppendNode(root) nodes.AppendNode(root)
nodes.Append(after.Tokens()) nodes.Append(after.Tokens())
@@ -242,7 +242,7 @@ func parseAttribute(nativeAttr *hclsyntax.Attribute, from, leadComments, lineCom
attr := &Attribute{ attr := &Attribute{
inTree: newInTree(), inTree: newInTree(),
} }
children := attr.inTree.children children := attr.children
{ {
cn := newNode(newComments(leadComments.Tokens())) cn := newNode(newComments(leadComments.Tokens()))
@@ -293,7 +293,7 @@ func parseBlock(nativeBlock *hclsyntax.Block, from, leadComments, lineComments,
block := &Block{ block := &Block{
inTree: newInTree(), inTree: newInTree(),
} }
children := block.inTree.children children := block.children
{ {
cn := newNode(newComments(leadComments.Tokens())) cn := newNode(newComments(leadComments.Tokens()))
@@ -314,7 +314,7 @@ func parseBlock(nativeBlock *hclsyntax.Block, from, leadComments, lineComments,
children.AppendNode(in) children.AppendNode(in)
} }
before, labelsNode, from := parseBlockLabels(nativeBlock, from) _, labelsNode, from := parseBlockLabels(nativeBlock, from)
block.labels = labelsNode block.labels = labelsNode
children.AppendNode(labelsNode) children.AppendNode(labelsNode)
@@ -377,7 +377,7 @@ func parseBlockLabels(nativeBlock *hclsyntax.Block, from inputTokens) (inputToke
func parseExpression(nativeExpr hclsyntax.Expression, from inputTokens) *node { func parseExpression(nativeExpr hclsyntax.Expression, from inputTokens) *node {
expr := newExpression() expr := newExpression()
children := expr.inTree.children children := expr.children
nativeVars := nativeExpr.Variables() 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) { func parseTraversal(nativeTraversal hcl.Traversal, from inputTokens) (before inputTokens, n *node, after inputTokens) {
traversal := newTraversal() traversal := newTraversal()
children := traversal.inTree.children children := traversal.children
before, from, after = from.Partition(nativeTraversal.SourceRange()) before, from, after = from.Partition(nativeTraversal.SourceRange())
stepAfter := from stepAfter := from
@@ -419,7 +419,7 @@ func parseTraversalStep(nativeStep hcl.Traverser, from inputTokens) (before inpu
case hcl.TraverseRoot, hcl.TraverseAttr: case hcl.TraverseRoot, hcl.TraverseAttr:
step := newTraverseName() step := newTraverseName()
children = step.inTree.children children = step.children
before, from, after = from.Partition(nativeStep.SourceRange()) before, from, after = from.Partition(nativeStep.SourceRange())
inBefore, token, inAfter := from.PartitionTypeSingle(hclsyntax.TokenIdent) inBefore, token, inAfter := from.PartitionTypeSingle(hclsyntax.TokenIdent)
name := newIdentifier(token) name := newIdentifier(token)
@@ -430,7 +430,7 @@ func parseTraversalStep(nativeStep hcl.Traverser, from inputTokens) (before inpu
case hcl.TraverseIndex: case hcl.TraverseIndex:
step := newTraverseIndex() step := newTraverseIndex()
children = step.inTree.children children = step.children
before, from, after = from.Partition(nativeStep.SourceRange()) before, from, after = from.Partition(nativeStep.SourceRange())
if inBefore, dot, from, ok := from.PartitionTypeOk(hclsyntax.TokenDot); ok { 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 // boundaries, such that the slice operator could be used to produce
// three token sequences for before, within, and after respectively: // three token sequences for before, within, and after respectively:
// //
// start, end := partitionTokens(toks, rng) // start, end := partitionTokens(toks, rng)
// before := toks[:start] // before := toks[:start]
// within := toks[start:end] // within := toks[start:end]
// after := toks[end:] // after := toks[end:]
// //
// This works best when the range is aligned with token boundaries (e.g. // 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 // because it was produced in terms of the scanner's result) but if that isn't
+2 -1
View File
@@ -19,7 +19,7 @@ func NewFile() *File {
file := &File{ file := &File{
inTree: newInTree(), inTree: newInTree(),
} }
file.body = file.inTree.children.Append(body) file.body = file.children.Append(body)
return file return file
} }
@@ -42,6 +42,7 @@ func Format(src []byte) []byte {
tokens := lexConfig(src) tokens := lexConfig(src)
format(tokens) format(tokens)
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
//nolint:errcheck // FIXME: Propogate errors upward.
tokens.WriteTo(buf) tokens.WriteTo(buf)
return buf.Bytes() return buf.Bytes()
} }
+1
View File
@@ -48,6 +48,7 @@ type Tokens []*Token
func (ts Tokens) Bytes() []byte { func (ts Tokens) Bytes() []byte {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
//nolint:errcheck // FIXME: Propogate errors upward.
ts.WriteTo(buf) ts.WriteTo(buf)
return buf.Bytes() return buf.Bytes()
} }
+3 -12
View File
@@ -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) { func parseObject(p *peeker) (node, hcl.Diagnostics) {
var diags hcl.Diagnostics var diags hcl.Diagnostics
@@ -375,7 +366,7 @@ func parseNumber(p *peeker) (node, hcl.Diagnostics) {
{ {
Severity: hcl.DiagError, Severity: hcl.DiagError,
Summary: "Invalid JSON number", 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, Subject: &tok.Range,
}, },
} }
@@ -394,7 +385,7 @@ func parseNumber(p *peeker) (node, hcl.Diagnostics) {
{ {
Severity: hcl.DiagError, Severity: hcl.DiagError,
Summary: "Invalid JSON number", 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, Subject: &tok.Range,
}, },
} }
@@ -448,7 +439,7 @@ func parseString(p *peeker) (node, hcl.Diagnostics) {
{ {
Severity: hcl.DiagError, Severity: hcl.DiagError,
Summary: "Invalid JSON string", 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, Subject: &errRange,
Context: contextRange, Context: contextRange,
}, },
+19 -11
View File
@@ -5,7 +5,7 @@ package json
import ( import (
"fmt" "fmt"
"io/ioutil" "io"
"os" "os"
"github.com/hashicorp/hcl/v2" "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. // 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. // 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) f, err := os.Open(filename)
if err != nil { if err != nil {
return nil, hcl.Diagnostics{ diags = append(diags, &hcl.Diagnostic{
{ Severity: hcl.DiagError,
Severity: hcl.DiagError, Summary: "Failed to open file",
Summary: "Failed to open file", Detail: fmt.Sprintf("The file %q could not be opened.", filename),
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 { if err != nil {
return nil, hcl.Diagnostics{ return nil, hcl.Diagnostics{
{ {
+14 -16
View File
@@ -107,23 +107,21 @@ func (mb mergedBodies) JustAttributes() (Attributes, Diagnostics) {
diags = append(diags, thisDiags...) diags = append(diags, thisDiags...)
} }
if thisAttrs != nil { for name, attr := range thisAttrs {
for name, attr := range thisAttrs { if existing := attrs[name]; existing != nil {
if existing := attrs[name]; existing != nil { diags = diags.Append(&Diagnostic{
diags = diags.Append(&Diagnostic{ Severity: DiagError,
Severity: DiagError, Summary: "Duplicate argument",
Summary: "Duplicate argument", Detail: fmt.Sprintf(
Detail: fmt.Sprintf( "Argument %q was already set at %s",
"Argument %q was already set at %s", name, existing.NameRange.String(),
name, existing.NameRange.String(), ),
), Subject: &attr.NameRange,
Subject: &attr.NameRange, })
}) continue
continue
}
attrs[name] = attr
} }
attrs[name] = attr
} }
} }
+4 -3
View File
@@ -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() { if !key.IsKnown() {
return cty.DynamicVal.WithSameMarks(collection), nil 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 return collection.GetAttr(attrName), nil
case ty.IsSetType(): case ty.IsSetType():
+11 -11
View File
@@ -74,7 +74,7 @@ func RelTraversalForExpr(expr Expression) (Traversal, Diagnostics) {
// For example, the following attribute has an expression that would produce // For example, the following attribute has an expression that would produce
// the keyword "foo": // the keyword "foo":
// //
// example = foo // example = foo
// //
// This function is a variant of AbsTraversalForExpr, which uses the same // This function is a variant of AbsTraversalForExpr, which uses the same
// interface on the given expression. This helper constrains the result // 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 // situations where one of a fixed set of keywords is required and arbitrary
// expressions are not allowed: // expressions are not allowed:
// //
// switch hcl.ExprAsKeyword(expr) { // switch hcl.ExprAsKeyword(expr) {
// case "allow": // case "allow":
// // (take suitable action for keyword "allow") // // (take suitable action for keyword "allow")
// case "deny": // case "deny":
// // (take suitable action for keyword "deny") // // (take suitable action for keyword "deny")
// default: // default:
// diags = append(diags, &hcl.Diagnostic{ // diags = append(diags, &hcl.Diagnostic{
// // ... "invalid keyword" diagnostic message ... // // ... "invalid keyword" diagnostic message ...
// }) // })
// } // }
// //
// The above approach will generate the same message for both the use of an // 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 // unrecognized keyword and for not using a keyword at all, which is usually
+18 -8
View File
@@ -5,6 +5,8 @@ import (
"unicode" "unicode"
) )
const nbsp = 0xA0
// WrapString wraps the given string within lim width in characters. // WrapString wraps the given string within lim width in characters.
// //
// Wrapping is currently naive and only happens at white-space. A future // 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 current uint
var wordBuf, spaceBuf bytes.Buffer var wordBuf, spaceBuf bytes.Buffer
var wordBufLen, spaceBufLen uint
for _, char := range s { for _, char := range s {
if char == '\n' { if char == '\n' {
if wordBuf.Len() == 0 { if wordBuf.Len() == 0 {
if current+uint(spaceBuf.Len()) > lim { if current+spaceBufLen > lim {
current = 0 current = 0
} else { } else {
current += uint(spaceBuf.Len()) current += spaceBufLen
spaceBuf.WriteTo(buf) spaceBuf.WriteTo(buf)
} }
spaceBuf.Reset() spaceBuf.Reset()
spaceBufLen = 0
} else { } else {
current += uint(spaceBuf.Len() + wordBuf.Len()) current += spaceBufLen + wordBufLen
spaceBuf.WriteTo(buf) spaceBuf.WriteTo(buf)
spaceBuf.Reset() spaceBuf.Reset()
spaceBufLen = 0
wordBuf.WriteTo(buf) wordBuf.WriteTo(buf)
wordBuf.Reset() wordBuf.Reset()
wordBufLen = 0
} }
buf.WriteRune(char) buf.WriteRune(char)
current = 0 current = 0
} else if unicode.IsSpace(char) { } else if unicode.IsSpace(char) && char != nbsp {
if spaceBuf.Len() == 0 || wordBuf.Len() > 0 { if spaceBuf.Len() == 0 || wordBuf.Len() > 0 {
current += uint(spaceBuf.Len() + wordBuf.Len()) current += spaceBufLen + wordBufLen
spaceBuf.WriteTo(buf) spaceBuf.WriteTo(buf)
spaceBuf.Reset() spaceBuf.Reset()
spaceBufLen = 0
wordBuf.WriteTo(buf) wordBuf.WriteTo(buf)
wordBuf.Reset() wordBuf.Reset()
wordBufLen = 0
} }
spaceBuf.WriteRune(char) spaceBuf.WriteRune(char)
spaceBufLen++
} else { } else {
wordBuf.WriteRune(char) 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') buf.WriteRune('\n')
current = 0 current = 0
spaceBuf.Reset() spaceBuf.Reset()
spaceBufLen = 0
} }
} }
} }
if wordBuf.Len() == 0 { if wordBuf.Len() == 0 {
if current+uint(spaceBuf.Len()) <= lim { if current+spaceBufLen <= lim {
spaceBuf.WriteTo(buf) spaceBuf.WriteTo(buf)
} }
} else { } else {
+8 -8
View File
@@ -14,7 +14,7 @@ type CapsuleOps struct {
// corresponding type. Conventionally this should return a string // corresponding type. Conventionally this should return a string
// representation of an expression that would produce an equivalent // representation of an expression that would produce an equivalent
// value. // value.
GoString func(val interface{}) string GoString func(val any) string
// TypeGoString provides the GoString implementation for the corresponding // TypeGoString provides the GoString implementation for the corresponding
// capsule type itself. // capsule type itself.
@@ -37,7 +37,7 @@ type CapsuleOps struct {
// //
// If RawEquals is nil then Equals must also be nil, selecting the default // If RawEquals is nil then Equals must also be nil, selecting the default
// pointer-identity comparison instead. // pointer-identity comparison instead.
Equals func(a, b interface{}) Value Equals func(a, b any) Value
// RawEquals provides the implementation of the RawEquals operation. // RawEquals provides the implementation of the RawEquals operation.
// This is called only with known, non-null values of the corresponding // 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 // If RawEquals is nil, values of the corresponding type are compared by
// pointer identity of the encapsulated value. // 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 // HashKey provides a hashing function for values of the corresponding
// capsule type. If defined, cty will use the resulting hashes as part // 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 // RawEquals to return true when given those values. If a given type
// does not uphold that assumption then sets including this type will // does not uphold that assumption then sets including this type will
// not behave correctly. // not behave correctly.
HashKey func(v interface{}) string HashKey func(v any) string
// ConversionFrom can provide conversions from the corresponding type to // ConversionFrom can provide conversions from the corresponding type to
// some other type when values of the corresponding type are used with // 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 // This function itself returns a function, allowing it to switch its
// behavior depending on the given source type. Return nil to indicate // behavior depending on the given source type. Return nil to indicate
// that no such conversion is available. // 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 // ConversionTo can provide conversions to the corresponding type from
// some other type when values of the corresponding type are used with // 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 // This function itself returns a function, allowing it to switch its
// behavior depending on the given destination type. Return nil to indicate // behavior depending on the given destination type. Return nil to indicate
// that no such conversion is available. // 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 // ExtensionData is an extension point for applications that wish to
// create their own extension features using capsule types. // 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, // should do so defensively: if the result of ExtensionData is not valid,
// prefer to ignore it or gracefully produce an error rather than causing // prefer to ignore it or gracefully produce an error rather than causing
// a panic. // a panic.
ExtensionData func(key interface{}) interface{} ExtensionData func(key any) any
} }
// noCapsuleOps is a pointer to a CapsuleOps with no functions set, which // 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. // on the purpose of and usage of this mechanism.
// //
// If CapsuleExtensionData is called on a non-capsule type then it will panic. // 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() ops := ty.CapsuleOps()
if ops.ExtensionData == nil { if ops.ExtensionData == nil {
return nil return nil
+2 -2
View File
@@ -4,7 +4,7 @@ import (
"github.com/zclconf/go-cty/cty" "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) rawConv := fn(inTy)
if rawConv == nil { if rawConv == nil {
return 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) rawConv := fn(outTy)
if rawConv == nil { if rawConv == nil {
return nil return nil
+55
View File
@@ -53,6 +53,9 @@ func MismatchMessage(got, want cty.Type) string {
case got.IsCollectionType() && want.IsCollectionType(): case got.IsCollectionType() && want.IsCollectionType():
return mismatchMessageCollectionsFromCollections(got, want) return mismatchMessageCollectionsFromCollections(got, want)
case !typesAreLikelyToCauseConfusion(got, want):
return fmt.Sprintf("%s required, but have %s", want.FriendlyName(), got.FriendlyName())
default: default:
// If we have nothing better to say, we'll just state what was required. // If we have nothing better to say, we'll just state what was required.
return want.FriendlyNameForConstraint() + " 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)) 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
View File
@@ -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
View File
@@ -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
View File
@@ -11,11 +11,11 @@ import (
// //
// Its usage pattern is as follows: // Its usage pattern is as follows:
// //
// it := val.ElementIterator() // it := val.ElementIterator()
// for it.Next() { // for it.Next() {
// key, val := it.Element() // key, val := it.Element()
// // ... // // ...
// } // }
type ElementIterator interface { type ElementIterator interface {
Next() bool Next() bool
Element() (key Value, value Value) Element() (key Value, value Value)
@@ -46,13 +46,13 @@ func elementIterator(val Value) ElementIterator {
case val.ty.IsListType(): case val.ty.IsListType():
return &listElementIterator{ return &listElementIterator{
ety: val.ty.ElementType(), ety: val.ty.ElementType(),
vals: val.v.([]interface{}), vals: val.v.([]any),
idx: -1, idx: -1,
} }
case val.ty.IsMapType(): case val.ty.IsMapType():
// We iterate the keys in a predictable lexicographical order so // We iterate the keys in a predictable lexicographical order so
// that results will always be stable given the same input map. // 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)) keys := make([]string, 0, len(rawMap))
for key := range rawMap { for key := range rawMap {
keys = append(keys, key) keys = append(keys, key)
@@ -66,7 +66,7 @@ func elementIterator(val Value) ElementIterator {
idx: -1, idx: -1,
} }
case val.ty.IsSetType(): case val.ty.IsSetType():
rawSet := val.v.(set.Set[interface{}]) rawSet := val.v.(set.Set[any])
return &setElementIterator{ return &setElementIterator{
ety: val.ty.ElementType(), ety: val.ty.ElementType(),
setIt: rawSet.Iterator(), setIt: rawSet.Iterator(),
@@ -74,7 +74,7 @@ func elementIterator(val Value) ElementIterator {
case val.ty.IsTupleType(): case val.ty.IsTupleType():
return &tupleElementIterator{ return &tupleElementIterator{
etys: val.ty.TupleElementTypes(), etys: val.ty.TupleElementTypes(),
vals: val.v.([]interface{}), vals: val.v.([]any),
idx: -1, idx: -1,
} }
case val.ty.IsObjectType(): case val.ty.IsObjectType():
@@ -89,7 +89,7 @@ func elementIterator(val Value) ElementIterator {
return &objectElementIterator{ return &objectElementIterator{
atys: atys, atys: atys,
vals: val.v.(map[string]interface{}), vals: val.v.(map[string]any),
attrNames: keys, attrNames: keys,
idx: -1, idx: -1,
} }
@@ -100,7 +100,7 @@ func elementIterator(val Value) ElementIterator {
type listElementIterator struct { type listElementIterator struct {
ety Type ety Type
vals []interface{} vals []any
idx int idx int
} }
@@ -119,7 +119,7 @@ func (it *listElementIterator) Next() bool {
type mapElementIterator struct { type mapElementIterator struct {
ety Type ety Type
vals map[string]interface{} vals map[string]any
keys []string keys []string
idx int idx int
} }
@@ -139,7 +139,7 @@ func (it *mapElementIterator) Next() bool {
type setElementIterator struct { type setElementIterator struct {
ety Type ety Type
setIt *set.Iterator[interface{}] setIt *set.Iterator[any]
} }
func (it *setElementIterator) Element() (Value, Value) { func (it *setElementIterator) Element() (Value, Value) {
@@ -156,7 +156,7 @@ func (it *setElementIterator) Next() bool {
type tupleElementIterator struct { type tupleElementIterator struct {
etys []Type etys []Type
vals []interface{} vals []any
idx int idx int
} }
@@ -175,7 +175,7 @@ func (it *tupleElementIterator) Next() bool {
type objectElementIterator struct { type objectElementIterator struct {
atys map[string]Type atys map[string]Type
vals map[string]interface{} vals map[string]any
attrNames []string attrNames []string
idx int idx int
} }
+2 -2
View File
@@ -11,7 +11,7 @@ type PathError struct {
Path Path 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 // We need to copy the Path because often our caller builds it by
// continually mutating the same underlying buffer. // continually mutating the same underlying buffer.
sPath := make(Path, len(path)) 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 // NewErrorf creates a new PathError for the current path by passing the
// given format and arguments to fmt.Errorf and then wrapping the result // given format and arguments to fmt.Errorf and then wrapping the result
// similarly to NewError. // 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...) return errorf(p, f, args...)
} }
+3 -3
View File
@@ -15,7 +15,7 @@ type ArgError struct {
Index int Index int
} }
func NewArgErrorf(i int, f string, args ...interface{}) error { func NewArgErrorf(i int, f string, args ...any) error {
return ArgError{ return ArgError{
error: fmt.Errorf(f, args...), error: fmt.Errorf(f, args...),
Index: i, 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) // into a normal error so that callers (expected to be language runtimes)
// are freed from having to deal with panics in buggy functions. // are freed from having to deal with panics in buggy functions.
type PanicError struct { type PanicError struct {
Value interface{} Value any
Stack []byte Stack []byte
} }
func errorForPanic(val interface{}) error { func errorForPanic(val any) error {
return PanicError{ return PanicError{
Value: val, Value: val,
Stack: debug.Stack(), Stack: debug.Stack(),
+2 -2
View File
@@ -11,12 +11,12 @@ import (
var valueType = reflect.TypeOf(cty.Value{}) var valueType = reflect.TypeOf(cty.Value{})
var typeType = reflect.TypeOf(cty.Type{}) 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 bigFloatType = reflect.TypeOf(big.Float{})
var bigIntType = reflect.TypeOf(big.Int{}) var bigIntType = reflect.TypeOf(big.Int{})
var emptyInterfaceType = reflect.TypeOf(interface{}(nil)) var emptyInterfaceType = reflect.TypeOf(any(nil))
var stringType = reflect.TypeOf("") var stringType = reflect.TypeOf("")
+6 -6
View File
@@ -23,7 +23,7 @@ import (
// presented from Go's perspective. These messages are thus not appropriate // presented from Go's perspective. These messages are thus not appropriate
// for display to end-users. An error returned from ToCtyValue represents a // for display to end-users. An error returned from ToCtyValue represents a
// bug in the calling program, not user error. // 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 // '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 // 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 // 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)) 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() inVals := rawSet.Values()
if len(inVals) == 0 { 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 // toCtyUnwrapPointer is a helper for dealing with Go pointers. It has three
// possible outcomes: // possible outcomes:
// //
// - Given value isn't a pointer, so it's just returned as-is. // - 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 // - Given value is a non-nil pointer, in which case it is dereferenced
// and the result returned. // and the result returned.
// - Given value is a nil pointer, in which case an invalid value is 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 // 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. // until a non-pointer value is found, or until a nil pointer is encountered.
+1 -1
View File
@@ -26,7 +26,7 @@ import (
// //
// The function will panic if given a non-pointer as the Go value target, // 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. // 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) tVal := reflect.ValueOf(target)
if tVal.Kind() != reflect.Ptr { if tVal.Kind() != reflect.Ptr {
panic("target value is not a pointer") panic("target value is not a pointer")
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"github.com/zclconf/go-cty/cty" "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 // to find a suitable cty.Type instance that could be used for a conversion
// with ToCtyValue. // with ToCtyValue.
// //
@@ -21,7 +21,7 @@ import (
// In particular, ImpliedType will never use capsule types in its returned // In particular, ImpliedType will never use capsule types in its returned
// type, because it cannot know the capsule types supported by the calling // type, because it cannot know the capsule types supported by the calling
// program. // program.
func ImpliedType(gv interface{}) (cty.Type, error) { func ImpliedType(gv any) (cty.Type, error) {
rt := reflect.TypeOf(gv) rt := reflect.TypeOf(gv)
var path cty.Path var path cty.Path
return impliedType(rt, path) return impliedType(rt, path)
+91 -44
View File
@@ -2,7 +2,10 @@ package cty
import ( import (
"fmt" "fmt"
"iter"
"strings" "strings"
"github.com/zclconf/go-cty/cty/ctymarks"
) )
// marker is an internal wrapper type used to add special "marks" to values. // 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 // an application that never marks a value does not need to worry about
// encountering marked values. // encountering marked values.
type marker struct { type marker struct {
realV interface{} realV any
marks ValueMarks marks ValueMarks
} }
// ValueMarks is a map, representing a set, of "mark" values associated with // 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. // 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. // 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 // If any of the arguments are already ValueMarks values then they'll be merged
// into the result, rather than used directly as individual marks. // into the result, rather than used directly as individual marks.
func NewValueMarks(marks ...interface{}) ValueMarks { func NewValueMarks(marks ...any) ValueMarks {
if len(marks) == 0 { if len(marks) == 0 {
return nil 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. // 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 { if mr, ok := val.v.(marker); ok {
_, ok := mr.marks[mark] _, ok := mr.marks[mark]
return ok return ok
@@ -119,21 +122,68 @@ func (val Value) HasMark(mark interface{}) bool {
return false 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 // ContainsMarked returns true if the receiving value or any value within it
// is marked. // is marked.
// //
// This operation is relatively expensive. If you only need a shallow result, // This operation is relatively expensive. If you only need a shallow result,
// use IsMarked instead. // use IsMarked instead.
func (val Value) ContainsMarked() bool { func (val Value) ContainsMarked() bool {
ret := false for _, v := range DeepValues(val) {
Walk(val, func(_ Path, v Value) (bool, error) {
if v.IsMarked() { if v.IsMarked() {
ret = true return true
return false, nil
} }
return true, nil }
}) return false
return ret
} }
func (val Value) assertUnmarked() { 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 // An application that never calls this method does not need to worry about
// handling marked values. // handling marked values.
func (val Value) Mark(mark interface{}) Value { func (val Value) Mark(mark any) Value {
if _, ok := mark.(ValueMarks); ok { if _, ok := mark.(ValueMarks); ok {
panic("cannot call Value.Mark with a ValueMarks value (use WithMarks instead)") 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 // markers to particular paths and returns the marked
// Value. // Value.
func (val Value) MarkWithPaths(pvm []PathValueMarks) 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}) ret, _ := TransformWithTransformer(val, &applyPathValueMarksTransformer{pvm})
return ret return ret
} }
@@ -257,24 +312,6 @@ func (val Value) Unmark() (Value, ValueMarks) {
}, marks }, 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 // UnmarkDeep is similar to Unmark, but it works with an entire nested structure
// rather than just the given value directly. // 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 // the returned marks set includes the superset of all of the marks encountered
// during the operation. // during the operation.
func (val Value) UnmarkDeep() (Value, ValueMarks) { func (val Value) UnmarkDeep() (Value, ValueMarks) {
t := unmarkTransformer{} retMarks := make(ValueMarks)
ret, _ := TransformWithTransformer(val, &t) retVal, _ := val.WrangleMarksDeep(func(mark any, path Path) (ctymarks.WrangleAction, error) {
retMarks[mark] = struct{}{}
marks := make(ValueMarks) return ctymarks.WrangleDrop, nil
for _, pvm := range t.pvm { })
for m, s := range pvm.Marks { return retVal, retMarks
marks[m] = s
}
}
return ret, marks
} }
// UnmarkDeepWithPaths is like UnmarkDeep, except it returns a slice // 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 // a caller to know which marks are associated with which paths
// in the Value. // in the Value.
func (val Value) UnmarkDeepWithPaths() (Value, []PathValueMarks) { func (val Value) UnmarkDeepWithPaths() (Value, []PathValueMarks) {
t := unmarkTransformer{} var pvm []PathValueMarks
ret, _ := TransformWithTransformer(val, &t) retVal, _ := val.WrangleMarksDeep(func(mark any, path Path) (ctymarks.WrangleAction, error) {
return ret, t.pvm 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 { func (val Value) unmarkForce() Value {
+289
View File
@@ -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
View File
@@ -134,7 +134,7 @@ func init() {
EmptyObject = Object(map[string]Type{}) EmptyObject = Object(map[string]Type{})
EmptyObjectVal = Value{ EmptyObjectVal = Value{
ty: EmptyObject, ty: EmptyObject,
v: map[string]interface{}{}, v: map[string]any{},
} }
} }
+1 -1
View File
@@ -43,5 +43,5 @@ type OrderedRules[T any] interface {
// Less returns true if and only if the first argument should sort before // 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 // the second argument. If the second argument should sort before the first
// or if there is no defined order for the values, return false. // or if there is no defined order for the values, return false.
Less(interface{}, interface{}) bool Less(any, any) bool
} }
+2 -2
View File
@@ -21,7 +21,7 @@ type ValueSet struct {
// ValueSet is just a thin wrapper around a set.Set with our value-oriented // 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 // "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. // 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. // 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))) return newValueSet(set.NewSet(newSetRules(ety)))
} }
func newValueSet(s set.Set[interface{}]) ValueSet { func newValueSet(s set.Set[any]) ValueSet {
return ValueSet{ return ValueSet{
s: s, s: s,
} }
+6 -6
View File
@@ -21,9 +21,9 @@ type setRules struct {
Type Type 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} return setRules{ety}
} }
@@ -43,14 +43,14 @@ func (val Value) Hash() int {
return int(crc32.ChecksumIEEE(hashBytes)) return int(crc32.ChecksumIEEE(hashBytes))
} }
func (r setRules) Hash(v interface{}) int { func (r setRules) Hash(v any) int {
return Value{ return Value{
ty: r.Type, ty: r.Type,
v: v, v: v,
}.Hash() }.Hash()
} }
func (r setRules) Equivalent(v1 interface{}, v2 interface{}) bool { func (r setRules) Equivalent(v1 any, v2 any) bool {
v1v := Value{ v1v := Value{
ty: r.Type, ty: r.Type,
v: v1, 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, // SameRules is only true if the other Rules instance is also a setRules struct,
// and the types are considered equal. // 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) rules, ok := other.(setRules)
if !ok { if !ok {
return false 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 // 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. // 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{ v1v := Value{
ty: r.Type, ty: r.Type,
v: v1, v: v1,
+1 -1
View File
@@ -74,7 +74,7 @@ func init() {
EmptyTuple = Tuple([]Type{}) EmptyTuple = Tuple([]Type{})
EmptyTupleVal = Value{ EmptyTupleVal = Value{
ty: EmptyTuple, ty: EmptyTuple,
v: []interface{}{}, v: []any{},
} }
} }
+1 -1
View File
@@ -15,7 +15,7 @@ type unknownType struct {
// on the unknown value, but all unknown values start as totally unknown // on the unknown value, but all unknown values start as totally unknown
// and we will also typically lose all unknown value refinements when // and we will also typically lose all unknown value refinements when
// round-tripping through serialization formats. // 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 // UnknownVal returns an Value that represents an unknown value of the given
// type. Unknown values can be used to represent a value that is // type. Unknown values can be used to represent a value that is
+5
View File
@@ -8,6 +8,11 @@ package cty
// represent unknowns, such as JSON, as long as the caller does not need to // represent unknowns, such as JSON, as long as the caller does not need to
// retain the unknown value information. // retain the unknown value information.
func UnknownAsNull(val Value) Value { func UnknownAsNull(val Value) Value {
if val.IsMarked() {
val, valMarks := val.Unmark()
return UnknownAsNull(val).WithMarks(valMarks)
}
ty := val.Type() ty := val.Type()
switch { switch {
case val.IsNull(): case val.IsNull():
+1 -1
View File
@@ -29,7 +29,7 @@ package cty
// rules than are offered by the built-in converter where necessary. // rules than are offered by the built-in converter where necessary.
type Value struct { type Value struct {
ty Type ty Type
v interface{} v any
} }
// Type returns the type of the value. // Type returns the type of the value.
+10 -10
View File
@@ -113,7 +113,7 @@ func NormalizeString(s string) string {
// by the key names and value types in the given map. // by the key names and value types in the given map.
func ObjectVal(attrs map[string]Value) Value { func ObjectVal(attrs map[string]Value) Value {
attrTypes := make(map[string]Type, len(attrs)) 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 { for attr, val := range attrs {
attr = NormalizeString(attr) attr = NormalizeString(attr)
@@ -131,7 +131,7 @@ func ObjectVal(attrs map[string]Value) Value {
// defined by the value types in the given slice. // defined by the value types in the given slice.
func TupleVal(elems []Value) Value { func TupleVal(elems []Value) Value {
elemTypes := make([]Type, len(elems)) elemTypes := make([]Type, len(elems))
elemVals := make([]interface{}, len(elems)) elemVals := make([]any, len(elems))
for i, val := range elems { for i, val := range elems {
elemTypes[i] = val.ty elemTypes[i] = val.ty
@@ -156,7 +156,7 @@ func ListVal(vals []Value) Value {
panic("must not call ListVal with empty slice") panic("must not call ListVal with empty slice")
} }
elementType := DynamicPseudoType elementType := DynamicPseudoType
rawList := make([]interface{}, len(vals)) rawList := make([]any, len(vals))
for i, val := range vals { for i, val := range vals {
if elementType == DynamicPseudoType { if elementType == DynamicPseudoType {
@@ -181,7 +181,7 @@ func ListVal(vals []Value) Value {
func ListValEmpty(element Type) Value { func ListValEmpty(element Type) Value {
return Value{ return Value{
ty: List(element), 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") panic("must not call MapVal with empty map")
} }
elementType := DynamicPseudoType elementType := DynamicPseudoType
rawMap := make(map[string]interface{}, len(vals)) rawMap := make(map[string]any, len(vals))
for key, val := range vals { for key, val := range vals {
if elementType == DynamicPseudoType { if elementType == DynamicPseudoType {
@@ -236,7 +236,7 @@ func MapVal(vals map[string]Value) Value {
func MapValEmpty(element Type) Value { func MapValEmpty(element Type) Value {
return Value{ return Value{
ty: Map(element), 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") panic("must not call SetVal with empty slice")
} }
elementType := DynamicPseudoType elementType := DynamicPseudoType
rawList := make([]interface{}, len(vals)) rawList := make([]any, len(vals))
var markSets []ValueMarks var markSets []ValueMarks
for i, val := range vals { for i, val := range vals {
@@ -286,7 +286,7 @@ func SetVal(vals []Value) Value {
rawList[i] = val.v rawList[i] = val.v
} }
rawVal := set.NewSetFromSlice(set.Rules[interface{}](setRules{elementType}), rawList) rawVal := set.NewSetFromSlice(set.Rules[any](setRules{elementType}), rawList)
return Value{ return Value{
ty: Set(elementType), ty: Set(elementType),
@@ -333,7 +333,7 @@ func SetValFromValueSet(s ValueSet) Value {
func SetValEmpty(element Type) Value { func SetValEmpty(element Type) Value {
return Value{ return Value{
ty: Set(element), 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 // 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 // the given wrapVal is not compatible with the given capsule type, or if
// wrapVal is not a pointer. // wrapVal is not a pointer.
func CapsuleVal(ty Type, wrapVal interface{}) Value { func CapsuleVal(ty Type, wrapVal any) Value {
if !ty.IsCapsuleType() { if !ty.IsCapsuleType() {
panic("not a capsule type") panic("not a capsule type")
} }
+85 -63
View File
@@ -2,6 +2,7 @@ package cty
import ( import (
"fmt" "fmt"
"iter"
"math/big" "math/big"
"github.com/zclconf/go-cty/cty/set" "github.com/zclconf/go-cty/cty/set"
@@ -13,7 +14,7 @@ func (val Value) GoString() string {
if val.IsMarked() { if val.IsMarked() {
unVal, marks := val.Unmark() unVal, marks := val.Unmark()
if len(marks) == 1 { if len(marks) == 1 {
var mark interface{} var mark any
for m := range marks { for m := range marks {
mark = m mark = m
} }
@@ -243,11 +244,11 @@ func (val Value) Equals(other Value) Value {
for attr, aty := range oty.AttrTypes { for attr, aty := range oty.AttrTypes {
lhs := Value{ lhs := Value{
ty: aty, ty: aty,
v: val.v.(map[string]interface{})[attr], v: val.v.(map[string]any)[attr],
} }
rhs := Value{ rhs := Value{
ty: aty, ty: aty,
v: other.v.(map[string]interface{})[attr], v: other.v.(map[string]any)[attr],
} }
eq := lhs.Equals(rhs) eq := lhs.Equals(rhs)
if !eq.IsKnown() { if !eq.IsKnown() {
@@ -264,11 +265,11 @@ func (val Value) Equals(other Value) Value {
for i, ety := range tty.ElemTypes { for i, ety := range tty.ElemTypes {
lhs := Value{ lhs := Value{
ty: ety, ty: ety,
v: val.v.([]interface{})[i], v: val.v.([]any)[i],
} }
rhs := Value{ rhs := Value{
ty: ety, ty: ety,
v: other.v.([]interface{})[i], v: other.v.([]any)[i],
} }
eq := lhs.Equals(rhs) eq := lhs.Equals(rhs)
if !eq.IsKnown() { if !eq.IsKnown() {
@@ -281,16 +282,16 @@ func (val Value) Equals(other Value) Value {
} }
case ty.IsListType(): case ty.IsListType():
ety := ty.typeImpl.(typeList).ElementTypeT 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 result = true
for i := range val.v.([]interface{}) { for i := range val.v.([]any) {
lhs := Value{ lhs := Value{
ty: ety, ty: ety,
v: val.v.([]interface{})[i], v: val.v.([]any)[i],
} }
rhs := Value{ rhs := Value{
ty: ety, ty: ety,
v: other.v.([]interface{})[i], v: other.v.([]any)[i],
} }
eq := lhs.Equals(rhs) eq := lhs.Equals(rhs)
if !eq.IsKnown() { if !eq.IsKnown() {
@@ -303,8 +304,8 @@ func (val Value) Equals(other Value) Value {
} }
} }
case ty.IsSetType(): case ty.IsSetType():
s1 := val.v.(set.Set[interface{}]) s1 := val.v.(set.Set[any])
s2 := other.v.(set.Set[interface{}]) s2 := other.v.(set.Set[any])
equal := true equal := true
// Two sets are equal if all of their values are known and all values // 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 result = equal
case ty.IsMapType(): case ty.IsMapType():
ety := ty.typeImpl.(typeMap).ElementTypeT 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 result = true
for k := range val.v.(map[string]interface{}) { for k := range val.v.(map[string]any) {
if _, ok := other.v.(map[string]interface{})[k]; !ok { if _, ok := other.v.(map[string]any)[k]; !ok {
result = false result = false
break break
} }
lhs := Value{ lhs := Value{
ty: ety, ty: ety,
v: val.v.(map[string]interface{})[k], v: val.v.(map[string]any)[k],
} }
rhs := Value{ rhs := Value{
ty: ety, ty: ety,
v: other.v.(map[string]interface{})[k], v: other.v.(map[string]any)[k],
} }
eq := lhs.Equals(rhs) eq := lhs.Equals(rhs)
if !eq.IsKnown() { if !eq.IsKnown() {
@@ -462,11 +463,11 @@ func (val Value) RawEquals(other Value) bool {
for attr, aty := range oty.AttrTypes { for attr, aty := range oty.AttrTypes {
lhs := Value{ lhs := Value{
ty: aty, ty: aty,
v: val.v.(map[string]interface{})[attr], v: val.v.(map[string]any)[attr],
} }
rhs := Value{ rhs := Value{
ty: aty, ty: aty,
v: other.v.(map[string]interface{})[attr], v: other.v.(map[string]any)[attr],
} }
eq := lhs.RawEquals(rhs) eq := lhs.RawEquals(rhs)
if !eq { if !eq {
@@ -479,11 +480,11 @@ func (val Value) RawEquals(other Value) bool {
for i, ety := range tty.ElemTypes { for i, ety := range tty.ElemTypes {
lhs := Value{ lhs := Value{
ty: ety, ty: ety,
v: val.v.([]interface{})[i], v: val.v.([]any)[i],
} }
rhs := Value{ rhs := Value{
ty: ety, ty: ety,
v: other.v.([]interface{})[i], v: other.v.([]any)[i],
} }
eq := lhs.RawEquals(rhs) eq := lhs.RawEquals(rhs)
if !eq { if !eq {
@@ -493,15 +494,15 @@ func (val Value) RawEquals(other Value) bool {
return true return true
case ty.IsListType(): case ty.IsListType():
ety := ty.typeImpl.(typeList).ElementTypeT ety := ty.typeImpl.(typeList).ElementTypeT
if len(val.v.([]interface{})) == len(other.v.([]interface{})) { if len(val.v.([]any)) == len(other.v.([]any)) {
for i := range val.v.([]interface{}) { for i := range val.v.([]any) {
lhs := Value{ lhs := Value{
ty: ety, ty: ety,
v: val.v.([]interface{})[i], v: val.v.([]any)[i],
} }
rhs := Value{ rhs := Value{
ty: ety, ty: ety,
v: other.v.([]interface{})[i], v: other.v.([]any)[i],
} }
eq := lhs.RawEquals(rhs) eq := lhs.RawEquals(rhs)
if !eq { if !eq {
@@ -545,18 +546,18 @@ func (val Value) RawEquals(other Value) bool {
} }
valUn, _ := val.Unmark() valUn, _ := val.Unmark()
otherUn, _ := other.Unmark() otherUn, _ := other.Unmark()
if len(valUn.v.(map[string]interface{})) == len(otherUn.v.(map[string]interface{})) { if len(valUn.v.(map[string]any)) == len(otherUn.v.(map[string]any)) {
for k := range valUn.v.(map[string]interface{}) { for k := range valUn.v.(map[string]any) {
if _, ok := otherUn.v.(map[string]interface{})[k]; !ok { if _, ok := otherUn.v.(map[string]any)[k]; !ok {
return false return false
} }
lhs := Value{ lhs := Value{
ty: ety, ty: ety,
v: valUn.v.(map[string]interface{})[k], v: valUn.v.(map[string]any)[k],
} }
rhs := Value{ rhs := Value{
ty: ety, ty: ety,
v: otherUn.v.(map[string]interface{})[k], v: otherUn.v.(map[string]any)[k],
} }
eq := lhs.RawEquals(rhs) eq := lhs.RawEquals(rhs)
if !eq { if !eq {
@@ -816,7 +817,7 @@ func (val Value) GetAttr(name string) Value {
return Value{ return Value{
ty: attrType, 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{ return Value{
ty: elty, ty: elty,
v: val.v.([]interface{})[index], v: val.v.([]any)[index],
} }
case val.Type().IsMapType(): case val.Type().IsMapType():
elty := val.Type().ElementType() elty := val.Type().ElementType()
@@ -895,7 +896,7 @@ func (val Value) Index(key Value) Value {
return Value{ return Value{
ty: elty, ty: elty,
v: val.v.(map[string]interface{})[keyStr], v: val.v.(map[string]any)[keyStr],
} }
case val.Type().IsTupleType(): case val.Type().IsTupleType():
if key.Type() == DynamicPseudoType { if key.Type() == DynamicPseudoType {
@@ -922,7 +923,7 @@ func (val Value) Index(key Value) Value {
return Value{ return Value{
ty: eltys[index], ty: eltys[index],
v: val.v.([]interface{})[index], v: val.v.([]any)[index],
} }
default: default:
panic("not a list, map, or tuple type") panic("not a list, map, or tuple type")
@@ -969,7 +970,7 @@ func (val Value) HasIndex(key Value) Value {
return False 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(): case val.Type().IsMapType():
if key.Type() == DynamicPseudoType { if key.Type() == DynamicPseudoType {
return UnknownVal(Bool).RefineNotNull() return UnknownVal(Bool).RefineNotNull()
@@ -986,7 +987,7 @@ func (val Value) HasIndex(key Value) Value {
} }
keyStr := key.v.(string) keyStr := key.v.(string)
_, exists := val.v.(map[string]interface{})[keyStr] _, exists := val.v.(map[string]any)[keyStr]
return BoolVal(exists) return BoolVal(exists)
case val.Type().IsTupleType(): case val.Type().IsTupleType():
@@ -1062,7 +1063,7 @@ func (val Value) HasElement(elem Value) Value {
return False return False
} }
s := val.v.(set.Set[interface{}]) s := val.v.(set.Set[any])
if !s.Has(elem.v) { if !s.Has(elem.v) {
return noMatchResult 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 be equal to other elements in the set, and thus they
// may or may not coalesce with other elements and produce fewer // may or may not coalesce with other elements and produce fewer
// items in the resulting set. // 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 storeLength == 1 || val.IsWhollyKnown() {
// If our set is wholly known then we know its length. // If our set is wholly known then we know its length.
// //
@@ -1164,7 +1165,7 @@ func (val Value) LengthInt() int {
switch { switch {
case val.ty.IsListType(): case val.ty.IsListType():
return len(val.v.([]interface{})) return len(val.v.([]any))
case val.ty.IsSetType(): case val.ty.IsSetType():
// NOTE: This is technically not correct in cases where the set // 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 // compatibility with callers that were relying on LengthInt rather
// than calling Length. Instead of panicking when a set contains an // than calling Length. Instead of panicking when a set contains an
// unknown value, LengthInt returns the largest possible length. // 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(): case val.ty.IsMapType():
return len(val.v.(map[string]interface{})) return len(val.v.(map[string]any))
default: default:
panic("value is not a collection") 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 // ElementIterator returns an ElementIterator for iterating the elements
// of the receiver, which must be a collection type, a tuple type, or an object // 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. // 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. // The element iterator produces keys and values matching what's described
// // for [Value.Elements]. New code should prefer to use [Value.Elements].
// 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.
func (val Value) ElementIterator() ElementIterator { func (val Value) ElementIterator() ElementIterator {
val.assertUnmarked() val.assertUnmarked()
if !val.IsKnown() { if !val.IsKnown() {
@@ -1222,7 +1242,7 @@ func (val Value) ElementIterator() ElementIterator {
} }
// CanIterateElements returns true if the receiver can support the // 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 { func (val Value) CanIterateElements() bool {
return canElementIterator(val) return canElementIterator(val)
} }
@@ -1232,7 +1252,9 @@ func (val Value) CanIterateElements() bool {
// will panic. // will panic.
// //
// ForEachElement uses ElementIterator internally, and so the values passed // 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 // Returns true if the iteration exited early due to the callback function
// returning true, or false if the loop ran to completion. // 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 result is the same pointer that was passed to CapsuleVal to create
// the value. Since cty considers values to be immutable, it is strongly // the value. Since cty considers values to be immutable, it is strongly
// recommended to treat the encapsulated value itself as immutable too. // recommended to treat the encapsulated value itself as immutable too.
func (val Value) EncapsulatedValue() interface{} { func (val Value) EncapsulatedValue() any {
val.assertUnmarked() val.assertUnmarked()
if !val.Type().IsCapsuleType() { if !val.Type().IsCapsuleType() {
panic("not a capsule-typed value") panic("not a capsule-typed value")
+28
View File
@@ -1,11 +1,19 @@
package cty package cty
import (
"io"
"iter"
)
// Walk visits all of the values in a possibly-complex structure, calling // Walk visits all of the values in a possibly-complex structure, calling
// a given function for each value. // a given function for each value.
// //
// For example, given a list of strings the callback would first be called // 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. // 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 // The callback function may prevent recursive visits to child values by
// returning false. The callback function my halt the walk altogether 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 // 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) 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 { func walk(path Path, val Value, cb func(Path, Value) (bool, error)) error {
deeper, err := cb(path, val) deeper, err := cb(path, val)
if err != nil { if err != nil {
+187
View File
@@ -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
View File
@@ -6,6 +6,7 @@ package term
import ( import (
"bytes" "bytes"
"fmt"
"io" "io"
"runtime" "runtime"
"strconv" "strconv"
@@ -36,6 +37,26 @@ var vt100EscapeCodes = EscapeCodes{
Reset: []byte{keyEscape, '[', '0', 'm'}, 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 // Terminal contains the state for running a VT100 terminal that is capable of
// reading lines of input. // reading lines of input.
type Terminal struct { type Terminal struct {
@@ -86,9 +107,14 @@ type Terminal struct {
remainder []byte remainder []byte
inBuf [256]byte inBuf [256]byte
// history contains previously entered commands so that they can be // History records and retrieves lines of input read by [ReadLine] which
// accessed with the up and down keys. // a user can retrieve and navigate using the up and down arrow keys.
history stRingBuffer //
// 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 // historyIndex stores the currently accessed history entry, where zero
// means the immediately previous entry. // means the immediately previous entry.
historyIndex int historyIndex int
@@ -111,6 +137,7 @@ func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
termHeight: 24, termHeight: 24,
echo: true, echo: true,
historyIndex: -1, historyIndex: -1,
History: &stRingBuffer{},
} }
} }
@@ -450,6 +477,23 @@ func visualLength(runes []rune) int {
return length 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 // handleKey processes the given key and, optionally, returns a line of text
// that the user has entered. // that the user has entered.
func (t *Terminal) handleKey(key rune) (line string, ok bool) { 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.pos = len(t.line)
t.moveCursorToPos(t.pos) t.moveCursorToPos(t.pos)
case keyUp: case keyUp:
entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1) entry, ok := t.historyAt(t.historyIndex + 1)
if !ok { if !ok {
return "", false return "", false
} }
@@ -516,7 +560,7 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
t.setLine(runes, len(runes)) t.setLine(runes, len(runes))
t.historyIndex-- t.historyIndex--
default: default:
entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1) entry, ok := t.historyAt(t.historyIndex - 1)
if ok { if ok {
t.historyIndex-- t.historyIndex--
runes := []rune(entry) runes := []rune(entry)
@@ -781,7 +825,7 @@ func (t *Terminal) readLine() (line string, err error) {
if lineOk { if lineOk {
if t.echo { if t.echo {
t.historyIndex = -1 t.historyIndex = -1
t.history.Add(line) t.historyAdd(line)
} }
if lineIsPasted { if lineIsPasted {
err = ErrPasteIndicator 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 // 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 // next most recent, and so on. If such an element doesn't exist then ok is
// false. // false.
func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) { func (s *stRingBuffer) At(n int) string {
if n < 0 || n >= s.size { 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 index := s.head - n
if index < 0 { if index < 0 {
index += s.max index += s.max
} }
return s.entries[index], true return s.entries[index]
} }
// readPasswordLine reads from reader until it finds \n or io.EOF. // readPasswordLine reads from reader until it finds \n or io.EOF.
+12 -11
View File
@@ -385,8 +385,8 @@ github.com/hashicorp/errwrap
# github.com/hashicorp/go-cleanhttp v0.5.2 # github.com/hashicorp/go-cleanhttp v0.5.2
## explicit; go 1.13 ## explicit; go 1.13
github.com/hashicorp/go-cleanhttp github.com/hashicorp/go-cleanhttp
# github.com/hashicorp/go-cty-funcs v0.0.0-20250210171435-dda779884a9f # github.com/hashicorp/go-cty-funcs v0.0.0-20250818135842-6aab67130928
## explicit; go 1.23 ## explicit; go 1.24
github.com/hashicorp/go-cty-funcs/cidr github.com/hashicorp/go-cty-funcs/cidr
github.com/hashicorp/go-cty-funcs/crypto github.com/hashicorp/go-cty-funcs/crypto
github.com/hashicorp/go-cty-funcs/encoding 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 # github.com/hashicorp/go-multierror v1.1.1
## explicit; go 1.13 ## explicit; go 1.13
github.com/hashicorp/go-multierror github.com/hashicorp/go-multierror
# github.com/hashicorp/hcl/v2 v2.23.0 # github.com/hashicorp/hcl/v2 v2.24.0
## explicit; go 1.18 ## explicit; go 1.23.0
github.com/hashicorp/hcl/v2 github.com/hashicorp/hcl/v2
github.com/hashicorp/hcl/v2/ext/customdecode github.com/hashicorp/hcl/v2/ext/customdecode
github.com/hashicorp/hcl/v2/ext/tryfunc github.com/hashicorp/hcl/v2/ext/tryfunc
@@ -442,8 +442,8 @@ github.com/mattn/go-runewidth
# github.com/mattn/go-shellwords v1.0.12 # github.com/mattn/go-shellwords v1.0.12
## explicit; go 1.13 ## explicit; go 1.13
github.com/mattn/go-shellwords github.com/mattn/go-shellwords
# github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 # github.com/mitchellh/go-wordwrap v1.0.1
## explicit ## explicit; go 1.14
github.com/mitchellh/go-wordwrap github.com/mitchellh/go-wordwrap
# github.com/mitchellh/hashstructure/v2 v2.0.2 # github.com/mitchellh/hashstructure/v2 v2.0.2
## explicit; go 1.14 ## explicit; go 1.14
@@ -691,10 +691,11 @@ github.com/x448/float16
# github.com/xhit/go-str2duration/v2 v2.1.0 # github.com/xhit/go-str2duration/v2 v2.1.0
## explicit; go 1.13 ## explicit; go 1.13
github.com/xhit/go-str2duration/v2 github.com/xhit/go-str2duration/v2
# github.com/zclconf/go-cty v1.16.2 # github.com/zclconf/go-cty v1.17.0
## explicit; go 1.18 ## explicit; go 1.23
github.com/zclconf/go-cty/cty github.com/zclconf/go-cty/cty
github.com/zclconf/go-cty/cty/convert github.com/zclconf/go-cty/cty/convert
github.com/zclconf/go-cty/cty/ctymarks
github.com/zclconf/go-cty/cty/ctystrings github.com/zclconf/go-cty/cty/ctystrings
github.com/zclconf/go-cty/cty/function github.com/zclconf/go-cty/cty/function
github.com/zclconf/go-cty/cty/function/stdlib 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 # go.yaml.in/yaml/v3 v3.0.4
## explicit; go 1.16 ## explicit; go 1.16
go.yaml.in/yaml/v3 go.yaml.in/yaml/v3
# golang.org/x/crypto v0.37.0 # golang.org/x/crypto v0.38.0
## explicit; go 1.23.0 ## explicit; go 1.23.0
golang.org/x/crypto/bcrypt golang.org/x/crypto/bcrypt
golang.org/x/crypto/blowfish golang.org/x/crypto/blowfish
@@ -857,10 +858,10 @@ golang.org/x/sys/plan9
golang.org/x/sys/unix golang.org/x/sys/unix
golang.org/x/sys/windows golang.org/x/sys/windows
golang.org/x/sys/windows/registry 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 ## explicit; go 1.23.0
golang.org/x/term golang.org/x/term
# golang.org/x/text v0.24.0 # golang.org/x/text v0.25.0
## explicit; go 1.23.0 ## explicit; go 1.23.0
golang.org/x/text/cases golang.org/x/text/cases
golang.org/x/text/feature/plural golang.org/x/text/feature/plural