From 65c082629336fb8872c71cfbe1adbcbd1e2f436c Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 18 Dec 2018 15:15:50 -0800 Subject: [PATCH] vendor: upgrade github.com/zclconf/go-cty This includes: - An additional check in the format stdlib function to fail if there are too many arguments given, rather than silently ignoring. - Refinements for the type unification behavior to allow unification of object/tuple types into weaker map/list types when no other unification is possible. - Improvements to the error messages for failed type conversions on collection and structural types to talk about mismatching element types where possible, rather than the outer value. --- go.mod | 2 +- go.sum | 2 + .../zclconf/go-cty/cty/convert/conversion.go | 12 +- .../go-cty/cty/convert/conversion_tuple.go | 71 +++++ .../go-cty/cty/convert/mismatch_msg.go | 85 ++++++ .../zclconf/go-cty/cty/convert/unify.go | 255 +++++++++++++++++- .../go-cty/cty/function/stdlib/format_fsm.go | 72 +++-- .../go-cty/cty/function/stdlib/format_fsm.rl | 16 ++ .../zclconf/go-cty/cty/msgpack/unmarshal.go | 3 +- vendor/modules.txt | 2 +- 10 files changed, 481 insertions(+), 39 deletions(-) create mode 100644 vendor/github.com/zclconf/go-cty/cty/convert/conversion_tuple.go diff --git a/go.mod b/go.mod index e22cf4df6a..97e0debeb5 100644 --- a/go.mod +++ b/go.mod @@ -128,7 +128,7 @@ require ( github.com/xanzy/ssh-agent v0.2.0 github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18 // indirect github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557 - github.com/zclconf/go-cty v0.0.0-20181129180422-88fbe721e0f8 + github.com/zclconf/go-cty v0.0.0-20181218225846-4fe1e489ee06 go.opencensus.io v0.17.0 // indirect go.uber.org/atomic v1.3.2 // indirect go.uber.org/multierr v1.1.0 // indirect diff --git a/go.sum b/go.sum index cd58fbcac7..920be0138f 100644 --- a/go.sum +++ b/go.sum @@ -324,6 +324,8 @@ github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557 h1:Jpn2j6wHkC9wJv5i github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/zclconf/go-cty v0.0.0-20181129180422-88fbe721e0f8 h1:imQXpaIqhN70pkpZ/a4ZMCiIi9CpaYE34f5/CAKWRs8= github.com/zclconf/go-cty v0.0.0-20181129180422-88fbe721e0f8/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= +github.com/zclconf/go-cty v0.0.0-20181218225846-4fe1e489ee06 h1:J3bfEicd/d85VHC6bPhrKb+2jO+Uquiy2bnkhia6XBA= +github.com/zclconf/go-cty v0.0.0-20181218225846-4fe1e489ee06/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= go.opencensus.io v0.17.0 h1:2Cu88MYg+1LU+WVD+NWwYhyP0kKgRlN9QjWGaX0jKTE= go.opencensus.io v0.17.0/go.mod h1:mp1VrMQxhlqqDpKvH4UcQUa4YwlzNmymAjPrDdfxNpI= go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= diff --git a/vendor/github.com/zclconf/go-cty/cty/convert/conversion.go b/vendor/github.com/zclconf/go-cty/cty/convert/conversion.go index ee866d1ccb..f9aacb4ee7 100644 --- a/vendor/github.com/zclconf/go-cty/cty/convert/conversion.go +++ b/vendor/github.com/zclconf/go-cty/cty/convert/conversion.go @@ -17,15 +17,14 @@ func getConversion(in cty.Type, out cty.Type, unsafe bool) conversion { // Wrap the conversion in some standard checks that we don't want to // have to repeat in every conversion function. return func(in cty.Value, path cty.Path) (cty.Value, error) { + if out == cty.DynamicPseudoType { + // Conversion to DynamicPseudoType always just passes through verbatim. + return in, nil + } if !in.IsKnown() { return cty.UnknownVal(out), nil } if in.IsNull() { - // If the output is a DynamicPseudoType, the type doesn't matter, - // so retain the type for proper null comparison. - if out == cty.DynamicPseudoType { - return in, nil - } // We'll pass through nulls, albeit type converted, and let // the caller deal with whatever handling they want to do in // case null values are considered valid in some applications. @@ -65,6 +64,9 @@ func getConversionKnown(in cty.Type, out cty.Type, unsafe bool) conversion { case out.IsObjectType() && in.IsObjectType(): return conversionObjectToObject(in, out, unsafe) + case out.IsTupleType() && in.IsTupleType(): + return conversionTupleToTuple(in, out, unsafe) + case out.IsListType() && (in.IsListType() || in.IsSetType()): inEty := in.ElementType() outEty := out.ElementType() diff --git a/vendor/github.com/zclconf/go-cty/cty/convert/conversion_tuple.go b/vendor/github.com/zclconf/go-cty/cty/convert/conversion_tuple.go new file mode 100644 index 0000000000..592980a701 --- /dev/null +++ b/vendor/github.com/zclconf/go-cty/cty/convert/conversion_tuple.go @@ -0,0 +1,71 @@ +package convert + +import ( + "github.com/zclconf/go-cty/cty" +) + +// conversionTupleToTuple returns a conversion that will make the input +// tuple type conform to the output tuple type, if possible. +// +// Conversion is possible only if the two tuple types have the same number +// of elements and the corresponding elements by index can be converted. +// +// Shallow tuple conversions work the same for both safe and unsafe modes, +// but the safety flag is passed on to recursive conversions and may thus +// limit which element type conversions are possible. +func conversionTupleToTuple(in, out cty.Type, unsafe bool) conversion { + inEtys := in.TupleElementTypes() + outEtys := out.TupleElementTypes() + + if len(inEtys) != len(outEtys) { + return nil // no conversion is possible + } + + elemConvs := make([]conversion, len(inEtys)) + + for i, outEty := range outEtys { + inEty := inEtys[i] + + if inEty.Equals(outEty) { + // No conversion needed, so we can leave this one nil. + continue + } + + elemConvs[i] = getConversion(inEty, outEty, unsafe) + if elemConvs[i] == nil { + // If a recursive conversion isn't available, then our top-level + // configuration is impossible too. + return nil + } + } + + // If we get here then a conversion is possible, using the element + // conversions given in elemConvs. + return func(val cty.Value, path cty.Path) (cty.Value, error) { + elemVals := make([]cty.Value, len(elemConvs)) + path = append(path, nil) + pathStep := &path[len(path)-1] + + i := 0 + for it := val.ElementIterator(); it.Next(); i++ { + _, val := it.Element() + var err error + + *pathStep = cty.IndexStep{ + Key: cty.NumberIntVal(int64(i)), + } + + conv := elemConvs[i] + if conv != nil { + val, err = conv(val, path) + if err != nil { + return cty.NilVal, err + } + } + + elemVals[i] = val + } + + return cty.TupleVal(elemVals), nil + } +} diff --git a/vendor/github.com/zclconf/go-cty/cty/convert/mismatch_msg.go b/vendor/github.com/zclconf/go-cty/cty/convert/mismatch_msg.go index 88a4a251be..581304ecd5 100644 --- a/vendor/github.com/zclconf/go-cty/cty/convert/mismatch_msg.go +++ b/vendor/github.com/zclconf/go-cty/cty/convert/mismatch_msg.go @@ -47,6 +47,12 @@ func MismatchMessage(got, want cty.Type) string { // find a common type to convert all of the object attributes to. return "all map elements must have the same type" + case (got.IsTupleType() || got.IsObjectType()) && want.IsCollectionType(): + return mismatchMessageCollectionsFromStructural(got, want) + + case got.IsCollectionType() && want.IsCollectionType(): + return mismatchMessageCollectionsFromCollections(got, want) + default: // If we have nothing better to say, we'll just state what was required. return want.FriendlyNameForConstraint() + " required" @@ -106,6 +112,7 @@ func mismatchMessageObjects(got, want cty.Type) string { switch { case len(missingAttrs) != 0: + sort.Strings(missingAttrs) switch len(missingAttrs) { case 1: return fmt.Sprintf("attribute %q is required", missingAttrs[0]) @@ -133,3 +140,81 @@ func mismatchMessageObjects(got, want cty.Type) string { return "incorrect object attributes" } } + +func mismatchMessageCollectionsFromStructural(got, want cty.Type) string { + // First some straightforward cases where the kind is just altogether wrong. + switch { + case want.IsListType() && !got.IsTupleType(): + return want.FriendlyNameForConstraint() + " required" + case want.IsSetType() && !got.IsTupleType(): + return want.FriendlyNameForConstraint() + " required" + case want.IsMapType() && !got.IsObjectType(): + return want.FriendlyNameForConstraint() + " required" + } + + // If the kinds are matched well enough then we'll move on to checking + // individual elements. + wantEty := want.ElementType() + switch { + case got.IsTupleType(): + for i, gotEty := range got.TupleElementTypes() { + if gotEty.Equals(wantEty) { + continue // exact match, so no problem + } + if conv := getConversion(gotEty, wantEty, true); conv != nil { + continue // conversion is available, so no problem + } + return fmt.Sprintf("element %d: %s", i, MismatchMessage(gotEty, wantEty)) + } + + // If we get down here then something weird is going on but we'll + // return a reasonable fallback message anyway. + return fmt.Sprintf("all elements must be %s", wantEty.FriendlyNameForConstraint()) + + case got.IsObjectType(): + for name, gotAty := range got.AttributeTypes() { + if gotAty.Equals(wantEty) { + continue // exact match, so no problem + } + if conv := getConversion(gotAty, wantEty, true); conv != nil { + continue // conversion is available, so no problem + } + return fmt.Sprintf("element %q: %s", name, MismatchMessage(gotAty, wantEty)) + } + + // If we get down here then something weird is going on but we'll + // return a reasonable fallback message anyway. + return fmt.Sprintf("all elements must be %s", wantEty.FriendlyNameForConstraint()) + + default: + // Should not be possible to get here since we only call this function + // with got as structural types, but... + return want.FriendlyNameForConstraint() + " required" + } +} + +func mismatchMessageCollectionsFromCollections(got, want cty.Type) string { + // First some straightforward cases where the kind is just altogether wrong. + switch { + case want.IsListType() && !(got.IsListType() || got.IsSetType()): + return want.FriendlyNameForConstraint() + " required" + case want.IsSetType() && !(got.IsListType() || got.IsSetType()): + return want.FriendlyNameForConstraint() + " required" + case want.IsMapType() && !got.IsMapType(): + return want.FriendlyNameForConstraint() + " required" + } + + // If the kinds are matched well enough then we'll check the element types. + gotEty := got.ElementType() + wantEty := want.ElementType() + noun := "element type" + switch { + case want.IsListType(): + noun = "list element type" + case want.IsSetType(): + noun = "set element type" + case want.IsMapType(): + noun = "map element type" + } + return fmt.Sprintf("incorrect %s: %s", noun, MismatchMessage(gotEty, wantEty)) +} diff --git a/vendor/github.com/zclconf/go-cty/cty/convert/unify.go b/vendor/github.com/zclconf/go-cty/cty/convert/unify.go index bd6736b4dc..f881dd208e 100644 --- a/vendor/github.com/zclconf/go-cty/cty/convert/unify.go +++ b/vendor/github.com/zclconf/go-cty/cty/convert/unify.go @@ -21,6 +21,39 @@ func unify(types []cty.Type, unsafe bool) (cty.Type, []Conversion) { return cty.NilType, nil } + // If all of the given types are of the same structural kind, we may be + // able to construct a new type that they can all be unified to, even if + // that is not one of the given types. We must try this before the general + // behavior below because in unsafe mode we can convert an object type to + // a subset of that type, which would be a much less useful conversion for + // unification purposes. + { + objectCt := 0 + tupleCt := 0 + dynamicCt := 0 + for _, ty := range types { + switch { + case ty.IsObjectType(): + objectCt++ + case ty.IsTupleType(): + tupleCt++ + case ty == cty.DynamicPseudoType: + dynamicCt++ + default: + break + } + } + switch { + case objectCt > 0 && (objectCt+dynamicCt) == len(types): + return unifyObjectTypes(types, unsafe, dynamicCt > 0) + case tupleCt > 0 && (tupleCt+dynamicCt) == len(types): + return unifyTupleTypes(types, unsafe, dynamicCt > 0) + case objectCt > 0 && tupleCt > 0: + // Can never unify object and tuple types since they have incompatible kinds + return cty.NilType, nil + } + } + prefOrder := sortTypes(types) // sortTypes gives us an order where earlier items are preferable as @@ -58,9 +91,225 @@ Preferences: return wantType, conversions } - // TODO: For structural types, try to invent a new type that they - // can all be unified to, by unifying their respective attributes. - // If we fall out here, no unification is possible return cty.NilType, nil } + +func unifyObjectTypes(types []cty.Type, unsafe bool, hasDynamic bool) (cty.Type, []Conversion) { + // If we had any dynamic types in the input here then we can't predict + // what path we'll take through here once these become known types, so + // we'll conservatively produce DynamicVal for these. + if hasDynamic { + return unifyAllAsDynamic(types) + } + + // There are two different ways we can succeed here: + // - If all of the given object types have the same set of attribute names + // and the corresponding types are all unifyable, then we construct that + // type. + // - If the given object types have different attribute names or their + // corresponding types are not unifyable, we'll instead try to unify + // all of the attribute types together to produce a map type. + // + // Our unification behavior is intentionally stricter than our conversion + // behavior for subset object types because user intent is different with + // unification use-cases: it makes sense to allow {"foo":true} to convert + // to emptyobjectval, but unifying an object with an attribute with the + // empty object type should be an error because unifying to the empty + // object type would be suprising and useless. + + firstAttrs := types[0].AttributeTypes() + for _, ty := range types[1:] { + thisAttrs := ty.AttributeTypes() + if len(thisAttrs) != len(firstAttrs) { + // If number of attributes is different then there can be no + // object type in common. + return unifyObjectTypesToMap(types, unsafe) + } + for name := range thisAttrs { + if _, ok := firstAttrs[name]; !ok { + // If attribute names don't exactly match then there can be + // no object type in common. + return unifyObjectTypesToMap(types, unsafe) + } + } + } + + // If we get here then we've proven that all of the given object types + // have exactly the same set of attribute names, though the types may + // differ. + retAtys := make(map[string]cty.Type) + atysAcross := make([]cty.Type, len(types)) + for name := range firstAttrs { + for i, ty := range types { + atysAcross[i] = ty.AttributeType(name) + } + retAtys[name], _ = unify(atysAcross, unsafe) + if retAtys[name] == cty.NilType { + // Cannot unify this attribute alone, which means that unification + // of everything down to a map type can't be possible either. + return cty.NilType, nil + } + } + retTy := cty.Object(retAtys) + + conversions := make([]Conversion, len(types)) + for i, ty := range types { + if ty.Equals(retTy) { + continue + } + if unsafe { + conversions[i] = GetConversionUnsafe(ty, retTy) + } else { + conversions[i] = GetConversion(ty, retTy) + } + if conversions[i] == nil { + // Shouldn't be reachable, since we were able to unify + return unifyObjectTypesToMap(types, unsafe) + } + } + + return retTy, conversions +} + +func unifyObjectTypesToMap(types []cty.Type, unsafe bool) (cty.Type, []Conversion) { + // This is our fallback case for unifyObjectTypes, where we see if we can + // construct a map type that can accept all of the attribute types. + + var atys []cty.Type + for _, ty := range types { + for _, aty := range ty.AttributeTypes() { + atys = append(atys, aty) + } + } + + ety, _ := unify(atys, unsafe) + if ety == cty.NilType { + return cty.NilType, nil + } + + retTy := cty.Map(ety) + conversions := make([]Conversion, len(types)) + for i, ty := range types { + if ty.Equals(retTy) { + continue + } + if unsafe { + conversions[i] = GetConversionUnsafe(ty, retTy) + } else { + conversions[i] = GetConversion(ty, retTy) + } + if conversions[i] == nil { + // Shouldn't be reachable, since we were able to unify + return unifyObjectTypesToMap(types, unsafe) + } + } + return retTy, conversions +} + +func unifyTupleTypes(types []cty.Type, unsafe bool, hasDynamic bool) (cty.Type, []Conversion) { + // If we had any dynamic types in the input here then we can't predict + // what path we'll take through here once these become known types, so + // we'll conservatively produce DynamicVal for these. + if hasDynamic { + return unifyAllAsDynamic(types) + } + + // There are two different ways we can succeed here: + // - If all of the given tuple types have the same sequence of element types + // and the corresponding types are all unifyable, then we construct that + // type. + // - If the given tuple types have different element types or their + // corresponding types are not unifyable, we'll instead try to unify + // all of the elements types together to produce a list type. + + firstEtys := types[0].TupleElementTypes() + for _, ty := range types[1:] { + thisEtys := ty.TupleElementTypes() + if len(thisEtys) != len(firstEtys) { + // If number of elements is different then there can be no + // tuple type in common. + return unifyTupleTypesToList(types, unsafe) + } + } + + // If we get here then we've proven that all of the given tuple types + // have the same number of elements, though the types may differ. + retEtys := make([]cty.Type, len(firstEtys)) + atysAcross := make([]cty.Type, len(types)) + for idx := range firstEtys { + for tyI, ty := range types { + atysAcross[tyI] = ty.TupleElementTypes()[idx] + } + retEtys[idx], _ = unify(atysAcross, unsafe) + if retEtys[idx] == cty.NilType { + // Cannot unify this element alone, which means that unification + // of everything down to a map type can't be possible either. + return cty.NilType, nil + } + } + retTy := cty.Tuple(retEtys) + + conversions := make([]Conversion, len(types)) + for i, ty := range types { + if ty.Equals(retTy) { + continue + } + if unsafe { + conversions[i] = GetConversionUnsafe(ty, retTy) + } else { + conversions[i] = GetConversion(ty, retTy) + } + if conversions[i] == nil { + // Shouldn't be reachable, since we were able to unify + return unifyTupleTypesToList(types, unsafe) + } + } + + return retTy, conversions +} + +func unifyTupleTypesToList(types []cty.Type, unsafe bool) (cty.Type, []Conversion) { + // This is our fallback case for unifyTupleTypes, where we see if we can + // construct a list type that can accept all of the element types. + + var etys []cty.Type + for _, ty := range types { + for _, ety := range ty.TupleElementTypes() { + etys = append(etys, ety) + } + } + + ety, _ := unify(etys, unsafe) + if ety == cty.NilType { + return cty.NilType, nil + } + + retTy := cty.List(ety) + conversions := make([]Conversion, len(types)) + for i, ty := range types { + if ty.Equals(retTy) { + continue + } + if unsafe { + conversions[i] = GetConversionUnsafe(ty, retTy) + } else { + conversions[i] = GetConversion(ty, retTy) + } + if conversions[i] == nil { + // Shouldn't be reachable, since we were able to unify + return unifyObjectTypesToMap(types, unsafe) + } + } + return retTy, conversions +} + +func unifyAllAsDynamic(types []cty.Type) (cty.Type, []Conversion) { + conversions := make([]Conversion, len(types)) + for i := range conversions { + conversions[i] = func(cty.Value) (cty.Value, error) { + return cty.DynamicVal, nil + } + } + return cty.DynamicPseudoType, conversions +} diff --git a/vendor/github.com/zclconf/go-cty/cty/function/stdlib/format_fsm.go b/vendor/github.com/zclconf/go-cty/cty/function/stdlib/format_fsm.go index 1dc1f4610d..32b1ac9712 100644 --- a/vendor/github.com/zclconf/go-cty/cty/function/stdlib/format_fsm.go +++ b/vendor/github.com/zclconf/go-cty/cty/function/stdlib/format_fsm.go @@ -11,9 +11,10 @@ import ( "unicode/utf8" "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" ) -// line 20 "format_fsm.go" +// line 21 "format_fsm.go" var _formatfsm_actions []byte = []byte{ 0, 1, 0, 1, 1, 1, 2, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8, @@ -86,15 +87,16 @@ const formatfsm_error int = 0 const formatfsm_en_main int = 8 -// line 19 "format_fsm.rl" +// line 20 "format_fsm.rl" func formatFSM(format string, a []cty.Value) (string, error) { var buf bytes.Buffer data := format nextArg := 1 // arg numbers are 1-based var verb formatVerb + highestArgIdx := 0 // zero means "none", since arg numbers are 1-based - // line 153 "format_fsm.rl" + // line 159 "format_fsm.rl" // Ragel state p := 0 // "Pointer" into data @@ -109,12 +111,12 @@ func formatFSM(format string, a []cty.Value) (string, error) { _ = te _ = eof - // line 121 "format_fsm.go" + // line 123 "format_fsm.go" { cs = formatfsm_start } - // line 126 "format_fsm.go" + // line 128 "format_fsm.go" { var _klen int var _trans int @@ -195,7 +197,7 @@ func formatFSM(format string, a []cty.Value) (string, error) { _acts++ switch _formatfsm_actions[_acts-1] { case 0: - // line 29 "format_fsm.rl" + // line 31 "format_fsm.rl" verb = formatVerb{ ArgNum: nextArg, @@ -205,12 +207,12 @@ func formatFSM(format string, a []cty.Value) (string, error) { ts = p case 1: - // line 38 "format_fsm.rl" + // line 40 "format_fsm.rl" buf.WriteByte(data[p]) case 4: - // line 49 "format_fsm.rl" + // line 51 "format_fsm.rl" // We'll try to slurp a whole UTF-8 sequence here, to give the user // better feedback. @@ -218,85 +220,89 @@ func formatFSM(format string, a []cty.Value) (string, error) { return buf.String(), fmt.Errorf("unrecognized format character %q at offset %d", r, p) case 5: - // line 56 "format_fsm.rl" + // line 58 "format_fsm.rl" verb.Sharp = true case 6: - // line 59 "format_fsm.rl" + // line 61 "format_fsm.rl" verb.Zero = true case 7: - // line 62 "format_fsm.rl" + // line 64 "format_fsm.rl" verb.Minus = true case 8: - // line 65 "format_fsm.rl" + // line 67 "format_fsm.rl" verb.Plus = true case 9: - // line 68 "format_fsm.rl" + // line 70 "format_fsm.rl" verb.Space = true case 10: - // line 72 "format_fsm.rl" + // line 74 "format_fsm.rl" verb.ArgNum = 0 case 11: - // line 75 "format_fsm.rl" + // line 77 "format_fsm.rl" verb.ArgNum = (10 * verb.ArgNum) + (int(data[p]) - '0') case 12: - // line 79 "format_fsm.rl" + // line 81 "format_fsm.rl" verb.HasWidth = true case 13: - // line 82 "format_fsm.rl" + // line 84 "format_fsm.rl" verb.Width = 0 case 14: - // line 85 "format_fsm.rl" + // line 87 "format_fsm.rl" verb.Width = (10 * verb.Width) + (int(data[p]) - '0') case 15: - // line 89 "format_fsm.rl" + // line 91 "format_fsm.rl" verb.HasPrec = true case 16: - // line 92 "format_fsm.rl" + // line 94 "format_fsm.rl" verb.Prec = 0 case 17: - // line 95 "format_fsm.rl" + // line 97 "format_fsm.rl" verb.Prec = (10 * verb.Prec) + (int(data[p]) - '0') case 18: - // line 99 "format_fsm.rl" + // line 101 "format_fsm.rl" verb.Mode = rune(data[p]) te = p + 1 verb.Raw = data[ts:te] verb.Offset = ts + if verb.ArgNum > highestArgIdx { + highestArgIdx = verb.ArgNum + } + err := formatAppend(&verb, &buf, a) if err != nil { return buf.String(), err } nextArg = verb.ArgNum + 1 - // line 324 "format_fsm.go" + // line 330 "format_fsm.go" } } @@ -319,22 +325,22 @@ func formatFSM(format string, a []cty.Value) (string, error) { __acts++ switch _formatfsm_actions[__acts-1] { case 2: - // line 42 "format_fsm.rl" + // line 44 "format_fsm.rl" case 3: - // line 45 "format_fsm.rl" + // line 47 "format_fsm.rl" return buf.String(), fmt.Errorf("invalid format string starting at offset %d", p) case 4: - // line 49 "format_fsm.rl" + // line 51 "format_fsm.rl" // We'll try to slurp a whole UTF-8 sequence here, to give the user // better feedback. r, _ := utf8.DecodeRuneInString(data[p:]) return buf.String(), fmt.Errorf("unrecognized format character %q at offset %d", r, p) - // line 363 "format_fsm.go" + // line 369 "format_fsm.go" } } } @@ -344,7 +350,7 @@ func formatFSM(format string, a []cty.Value) (string, error) { } } - // line 171 "format_fsm.rl" + // line 177 "format_fsm.rl" // If we fall out here without being in a final state then we've // encountered something that the scanner can't match, which should @@ -354,5 +360,15 @@ func formatFSM(format string, a []cty.Value) (string, error) { return buf.String(), fmt.Errorf("extraneous characters beginning at offset %d", p) } + if highestArgIdx < len(a) { + // Extraneous args are an error, to more easily detect mistakes + firstBad := highestArgIdx + 1 + if highestArgIdx == 0 { + // Custom error message for this case + return buf.String(), function.NewArgErrorf(firstBad, "too many arguments; no verbs in format string") + } + return buf.String(), function.NewArgErrorf(firstBad, "too many arguments; only %d used by format string", highestArgIdx) + } + return buf.String(), nil } diff --git a/vendor/github.com/zclconf/go-cty/cty/function/stdlib/format_fsm.rl b/vendor/github.com/zclconf/go-cty/cty/function/stdlib/format_fsm.rl index dbaa91c6b0..3c642d9e14 100644 --- a/vendor/github.com/zclconf/go-cty/cty/function/stdlib/format_fsm.rl +++ b/vendor/github.com/zclconf/go-cty/cty/function/stdlib/format_fsm.rl @@ -12,6 +12,7 @@ import ( "unicode/utf8" "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" ) %%{ @@ -23,6 +24,7 @@ func formatFSM(format string, a []cty.Value) (string, error) { data := format nextArg := 1 // arg numbers are 1-based var verb formatVerb + highestArgIdx := 0 // zero means "none", since arg numbers are 1-based %%{ @@ -102,6 +104,10 @@ func formatFSM(format string, a []cty.Value) (string, error) { verb.Raw = data[ts:te] verb.Offset = ts + if verb.ArgNum > highestArgIdx { + highestArgIdx = verb.ArgNum + } + err := formatAppend(&verb, &buf, a) if err != nil { return buf.String(), err @@ -178,5 +184,15 @@ func formatFSM(format string, a []cty.Value) (string, error) { return buf.String(), fmt.Errorf("extraneous characters beginning at offset %d", p) } + if highestArgIdx < len(a) { + // Extraneous args are an error, to more easily detect mistakes + firstBad := highestArgIdx+1 + if highestArgIdx == 0 { + // Custom error message for this case + return buf.String(), function.NewArgErrorf(firstBad, "too many arguments; no verbs in format string") + } + return buf.String(), function.NewArgErrorf(firstBad, "too many arguments; only %d used by format string", highestArgIdx) + } + return buf.String(), nil } diff --git a/vendor/github.com/zclconf/go-cty/cty/msgpack/unmarshal.go b/vendor/github.com/zclconf/go-cty/cty/msgpack/unmarshal.go index 16e1c09321..a5bd3c10bb 100644 --- a/vendor/github.com/zclconf/go-cty/cty/msgpack/unmarshal.go +++ b/vendor/github.com/zclconf/go-cty/cty/msgpack/unmarshal.go @@ -276,7 +276,8 @@ func unmarshalObject(dec *msgpack.Decoder, atys map[string]cty.Type, path cty.Pa case length == 0: return cty.ObjectVal(nil), nil case length != len(atys): - return cty.DynamicVal, path.NewErrorf("an object with %d attributes is required", len(atys)) + return cty.DynamicVal, path.NewErrorf("an object with %d attributes is required (%d given)", + len(atys), length) } vals := make(map[string]cty.Value, length) diff --git a/vendor/modules.txt b/vendor/modules.txt index aeab187c9d..15d291db3e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -480,7 +480,7 @@ github.com/vmihailenco/msgpack/codes github.com/xanzy/ssh-agent # github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557 github.com/xlab/treeprint -# github.com/zclconf/go-cty v0.0.0-20181129180422-88fbe721e0f8 +# github.com/zclconf/go-cty v0.0.0-20181218225846-4fe1e489ee06 github.com/zclconf/go-cty/cty github.com/zclconf/go-cty/cty/gocty github.com/zclconf/go-cty/cty/convert