github.com/terraform-linters/tflint@v0.51.2-0.20240520175844-3750771571b6/terraform/lang/funcs/collection.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package funcs 5 6 import ( 7 "errors" 8 "fmt" 9 "math/big" 10 "sort" 11 12 "github.com/zclconf/go-cty/cty" 13 "github.com/zclconf/go-cty/cty/convert" 14 "github.com/zclconf/go-cty/cty/function" 15 "github.com/zclconf/go-cty/cty/function/stdlib" 16 "github.com/zclconf/go-cty/cty/gocty" 17 ) 18 19 var LengthFunc = function.New(&function.Spec{ 20 Params: []function.Parameter{ 21 { 22 Name: "value", 23 Type: cty.DynamicPseudoType, 24 AllowDynamicType: true, 25 AllowUnknown: true, 26 AllowMarked: true, 27 }, 28 }, 29 Type: func(args []cty.Value) (cty.Type, error) { 30 collTy := args[0].Type() 31 switch { 32 case collTy == cty.String || collTy.IsTupleType() || collTy.IsObjectType() || collTy.IsListType() || collTy.IsMapType() || collTy.IsSetType() || collTy == cty.DynamicPseudoType: 33 return cty.Number, nil 34 default: 35 return cty.Number, errors.New("argument must be a string, a collection type, or a structural type") 36 } 37 }, 38 RefineResult: refineNotNull, 39 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 40 coll := args[0] 41 collTy := args[0].Type() 42 marks := coll.Marks() 43 switch { 44 case collTy == cty.DynamicPseudoType: 45 return cty.UnknownVal(cty.Number).WithMarks(marks), nil 46 case collTy.IsTupleType(): 47 l := len(collTy.TupleElementTypes()) 48 return cty.NumberIntVal(int64(l)).WithMarks(marks), nil 49 case collTy.IsObjectType(): 50 l := len(collTy.AttributeTypes()) 51 return cty.NumberIntVal(int64(l)).WithMarks(marks), nil 52 case collTy == cty.String: 53 // We'll delegate to the cty stdlib strlen function here, because 54 // it deals with all of the complexities of tokenizing unicode 55 // grapheme clusters. 56 return stdlib.Strlen(coll) 57 case collTy.IsListType() || collTy.IsSetType() || collTy.IsMapType(): 58 return coll.Length(), nil 59 default: 60 // Should never happen, because of the checks in our Type func above 61 return cty.UnknownVal(cty.Number), errors.New("impossible value type for length(...)") 62 } 63 }, 64 }) 65 66 // AllTrueFunc constructs a function that returns true if all elements of the 67 // list are true. If the list is empty, return true. 68 var AllTrueFunc = function.New(&function.Spec{ 69 Params: []function.Parameter{ 70 { 71 Name: "list", 72 Type: cty.List(cty.Bool), 73 }, 74 }, 75 Type: function.StaticReturnType(cty.Bool), 76 RefineResult: refineNotNull, 77 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 78 result := cty.True 79 for it := args[0].ElementIterator(); it.Next(); { 80 _, v := it.Element() 81 if !v.IsKnown() { 82 return cty.UnknownVal(cty.Bool), nil 83 } 84 if v.IsNull() { 85 return cty.False, nil 86 } 87 result = result.And(v) 88 if result.False() { 89 return cty.False, nil 90 } 91 } 92 return result, nil 93 }, 94 }) 95 96 // AnyTrueFunc constructs a function that returns true if any element of the 97 // list is true. If the list is empty, return false. 98 var AnyTrueFunc = function.New(&function.Spec{ 99 Params: []function.Parameter{ 100 { 101 Name: "list", 102 Type: cty.List(cty.Bool), 103 }, 104 }, 105 Type: function.StaticReturnType(cty.Bool), 106 RefineResult: refineNotNull, 107 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 108 result := cty.False 109 var hasUnknown bool 110 for it := args[0].ElementIterator(); it.Next(); { 111 _, v := it.Element() 112 if !v.IsKnown() { 113 hasUnknown = true 114 continue 115 } 116 if v.IsNull() { 117 continue 118 } 119 result = result.Or(v) 120 if result.True() { 121 return cty.True, nil 122 } 123 } 124 if hasUnknown { 125 return cty.UnknownVal(cty.Bool), nil 126 } 127 return result, nil 128 }, 129 }) 130 131 // CoalesceFunc constructs a function that takes any number of arguments and 132 // returns the first one that isn't empty. This function was copied from go-cty 133 // stdlib and modified so that it returns the first *non-empty* non-null element 134 // from a sequence, instead of merely the first non-null. 135 var CoalesceFunc = function.New(&function.Spec{ 136 Params: []function.Parameter{}, 137 VarParam: &function.Parameter{ 138 Name: "vals", 139 Type: cty.DynamicPseudoType, 140 AllowUnknown: true, 141 AllowDynamicType: true, 142 AllowNull: true, 143 }, 144 RefineResult: refineNotNull, 145 Type: func(args []cty.Value) (ret cty.Type, err error) { 146 argTypes := make([]cty.Type, len(args)) 147 for i, val := range args { 148 argTypes[i] = val.Type() 149 } 150 retType, _ := convert.UnifyUnsafe(argTypes) 151 if retType == cty.NilType { 152 return cty.NilType, errors.New("all arguments must have the same type") 153 } 154 return retType, nil 155 }, 156 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 157 for _, argVal := range args { 158 // We already know this will succeed because of the checks in our Type func above 159 argVal, _ = convert.Convert(argVal, retType) 160 if !argVal.IsKnown() { 161 return cty.UnknownVal(retType), nil 162 } 163 if argVal.IsNull() { 164 continue 165 } 166 if retType == cty.String && argVal.RawEquals(cty.StringVal("")) { 167 continue 168 } 169 170 return argVal, nil 171 } 172 return cty.NilVal, errors.New("no non-null, non-empty-string arguments") 173 }, 174 }) 175 176 // IndexFunc constructs a function that finds the element index for a given value in a list. 177 var IndexFunc = function.New(&function.Spec{ 178 Params: []function.Parameter{ 179 { 180 Name: "list", 181 Type: cty.DynamicPseudoType, 182 }, 183 { 184 Name: "value", 185 Type: cty.DynamicPseudoType, 186 }, 187 }, 188 Type: function.StaticReturnType(cty.Number), 189 RefineResult: refineNotNull, 190 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 191 if !(args[0].Type().IsListType() || args[0].Type().IsTupleType()) { 192 return cty.NilVal, errors.New("argument must be a list or tuple") 193 } 194 195 if !args[0].IsKnown() { 196 return cty.UnknownVal(cty.Number), nil 197 } 198 199 if args[0].LengthInt() == 0 { // Easy path 200 return cty.NilVal, errors.New("cannot search an empty list") 201 } 202 203 for it := args[0].ElementIterator(); it.Next(); { 204 i, v := it.Element() 205 eq, err := stdlib.Equal(v, args[1]) 206 if err != nil { 207 return cty.NilVal, err 208 } 209 if !eq.IsKnown() { 210 return cty.UnknownVal(cty.Number), nil 211 } 212 if eq.True() { 213 return i, nil 214 } 215 } 216 return cty.NilVal, errors.New("item not found") 217 218 }, 219 }) 220 221 // LookupFunc constructs a function that performs dynamic lookups of map types. 222 var LookupFunc = function.New(&function.Spec{ 223 Params: []function.Parameter{ 224 { 225 Name: "inputMap", 226 Type: cty.DynamicPseudoType, 227 AllowMarked: true, 228 }, 229 { 230 Name: "key", 231 Type: cty.String, 232 AllowMarked: true, 233 }, 234 }, 235 VarParam: &function.Parameter{ 236 Name: "default", 237 Type: cty.DynamicPseudoType, 238 AllowUnknown: true, 239 AllowDynamicType: true, 240 AllowNull: true, 241 AllowMarked: true, 242 }, 243 Type: func(args []cty.Value) (ret cty.Type, err error) { 244 if len(args) < 1 || len(args) > 3 { 245 return cty.NilType, fmt.Errorf("lookup() takes two or three arguments, got %d", len(args)) 246 } 247 248 ty := args[0].Type() 249 250 switch { 251 case ty.IsObjectType(): 252 if !args[1].IsKnown() { 253 return cty.DynamicPseudoType, nil 254 } 255 256 keyVal, _ := args[1].Unmark() 257 key := keyVal.AsString() 258 if ty.HasAttribute(key) { 259 return args[0].GetAttr(key).Type(), nil 260 } else if len(args) == 3 { 261 // if the key isn't found but a default is provided, 262 // return the default type 263 return args[2].Type(), nil 264 } 265 return cty.DynamicPseudoType, function.NewArgErrorf(0, "the given object has no attribute %q", key) 266 case ty.IsMapType(): 267 if len(args) == 3 { 268 _, err = convert.Convert(args[2], ty.ElementType()) 269 if err != nil { 270 return cty.NilType, function.NewArgErrorf(2, "the default value must have the same type as the map elements") 271 } 272 } 273 return ty.ElementType(), nil 274 default: 275 return cty.NilType, function.NewArgErrorf(0, "lookup() requires a map as the first argument") 276 } 277 }, 278 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 279 var defaultVal cty.Value 280 defaultValueSet := false 281 282 if len(args) == 3 { 283 // intentionally leave default value marked 284 defaultVal = args[2] 285 defaultValueSet = true 286 } 287 288 // keep track of marks from the collection and key 289 var markses []cty.ValueMarks 290 291 // unmark collection, retain marks to reapply later 292 mapVar, mapMarks := args[0].Unmark() 293 markses = append(markses, mapMarks) 294 295 // include marks on the key in the result 296 keyVal, keyMarks := args[1].Unmark() 297 if len(keyMarks) > 0 { 298 markses = append(markses, keyMarks) 299 } 300 lookupKey := keyVal.AsString() 301 302 if !mapVar.IsKnown() { 303 return cty.UnknownVal(retType).WithMarks(markses...), nil 304 } 305 306 if mapVar.Type().IsObjectType() { 307 if mapVar.Type().HasAttribute(lookupKey) { 308 return mapVar.GetAttr(lookupKey).WithMarks(markses...), nil 309 } 310 } else if mapVar.HasIndex(cty.StringVal(lookupKey)) == cty.True { 311 return mapVar.Index(cty.StringVal(lookupKey)).WithMarks(markses...), nil 312 } 313 314 if defaultValueSet { 315 defaultVal, err = convert.Convert(defaultVal, retType) 316 if err != nil { 317 return cty.NilVal, err 318 } 319 return defaultVal.WithMarks(markses...), nil 320 } 321 322 return cty.UnknownVal(cty.DynamicPseudoType), fmt.Errorf( 323 "lookup failed to find key %s", redactIfSensitive(lookupKey, keyMarks)) 324 }, 325 }) 326 327 // MatchkeysFunc constructs a function that constructs a new list by taking a 328 // subset of elements from one list whose indexes match the corresponding 329 // indexes of values in another list. 330 var MatchkeysFunc = function.New(&function.Spec{ 331 Params: []function.Parameter{ 332 { 333 Name: "values", 334 Type: cty.List(cty.DynamicPseudoType), 335 }, 336 { 337 Name: "keys", 338 Type: cty.List(cty.DynamicPseudoType), 339 }, 340 { 341 Name: "searchset", 342 Type: cty.List(cty.DynamicPseudoType), 343 }, 344 }, 345 Type: func(args []cty.Value) (cty.Type, error) { 346 ty, _ := convert.UnifyUnsafe([]cty.Type{args[1].Type(), args[2].Type()}) 347 if ty == cty.NilType { 348 return cty.NilType, errors.New("keys and searchset must be of the same type") 349 } 350 351 // the return type is based on args[0] (values) 352 return args[0].Type(), nil 353 }, 354 RefineResult: refineNotNull, 355 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 356 if !args[0].IsKnown() { 357 return cty.UnknownVal(cty.List(retType.ElementType())), nil 358 } 359 360 if args[0].LengthInt() != args[1].LengthInt() { 361 return cty.ListValEmpty(retType.ElementType()), errors.New("length of keys and values should be equal") 362 } 363 364 output := make([]cty.Value, 0) 365 values := args[0] 366 367 // Keys and searchset must be the same type. 368 // We can skip error checking here because we've already verified that 369 // they can be unified in the Type function 370 ty, _ := convert.UnifyUnsafe([]cty.Type{args[1].Type(), args[2].Type()}) 371 keys, _ := convert.Convert(args[1], ty) 372 searchset, _ := convert.Convert(args[2], ty) 373 374 // if searchset is empty, return an empty list. 375 if searchset.LengthInt() == 0 { 376 return cty.ListValEmpty(retType.ElementType()), nil 377 } 378 379 if !values.IsWhollyKnown() || !keys.IsWhollyKnown() { 380 return cty.UnknownVal(retType), nil 381 } 382 383 i := 0 384 for it := keys.ElementIterator(); it.Next(); { 385 _, key := it.Element() 386 for iter := searchset.ElementIterator(); iter.Next(); { 387 _, search := iter.Element() 388 eq, err := stdlib.Equal(key, search) 389 if err != nil { 390 return cty.NilVal, err 391 } 392 if !eq.IsKnown() { 393 return cty.ListValEmpty(retType.ElementType()), nil 394 } 395 if eq.True() { 396 v := values.Index(cty.NumberIntVal(int64(i))) 397 output = append(output, v) 398 break 399 } 400 } 401 i++ 402 } 403 404 // if we haven't matched any key, then output is an empty list. 405 if len(output) == 0 { 406 return cty.ListValEmpty(retType.ElementType()), nil 407 } 408 return cty.ListVal(output), nil 409 }, 410 }) 411 412 // OneFunc returns either the first element of a one-element list, or null 413 // if given a zero-element list. 414 var OneFunc = function.New(&function.Spec{ 415 Params: []function.Parameter{ 416 { 417 Name: "list", 418 Type: cty.DynamicPseudoType, 419 }, 420 }, 421 Type: func(args []cty.Value) (cty.Type, error) { 422 ty := args[0].Type() 423 switch { 424 case ty.IsListType() || ty.IsSetType(): 425 return ty.ElementType(), nil 426 case ty.IsTupleType(): 427 etys := ty.TupleElementTypes() 428 switch len(etys) { 429 case 0: 430 // No specific type information, so we'll ultimately return 431 // a null value of unknown type. 432 return cty.DynamicPseudoType, nil 433 case 1: 434 return etys[0], nil 435 } 436 } 437 return cty.NilType, function.NewArgErrorf(0, "must be a list, set, or tuple value with either zero or one elements") 438 }, 439 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 440 val := args[0] 441 ty := val.Type() 442 443 // Our parameter spec above doesn't set AllowUnknown or AllowNull, 444 // so we can assume our top-level collection is both known and non-null 445 // in here. 446 447 switch { 448 case ty.IsListType() || ty.IsSetType(): 449 lenVal := val.Length() 450 if !lenVal.IsKnown() { 451 return cty.UnknownVal(retType), nil 452 } 453 var l int 454 err := gocty.FromCtyValue(lenVal, &l) 455 if err != nil { 456 // It would be very strange to get here, because that would 457 // suggest that the length is either not a number or isn't 458 // an integer, which would suggest a bug in cty. 459 return cty.NilVal, fmt.Errorf("invalid collection length: %s", err) 460 } 461 switch l { 462 case 0: 463 return cty.NullVal(retType), nil 464 case 1: 465 var ret cty.Value 466 // We'll use an iterator here because that works for both lists 467 // and sets, whereas indexing directly would only work for lists. 468 // Since we've just checked the length, we should only actually 469 // run this loop body once. 470 for it := val.ElementIterator(); it.Next(); { 471 _, ret = it.Element() 472 } 473 return ret, nil 474 } 475 case ty.IsTupleType(): 476 etys := ty.TupleElementTypes() 477 switch len(etys) { 478 case 0: 479 return cty.NullVal(retType), nil 480 case 1: 481 ret := val.Index(cty.NumberIntVal(0)) 482 return ret, nil 483 } 484 } 485 return cty.NilVal, function.NewArgErrorf(0, "must be a list, set, or tuple value with either zero or one elements") 486 }, 487 }) 488 489 // SumFunc constructs a function that returns the sum of all 490 // numbers provided in a list 491 var SumFunc = function.New(&function.Spec{ 492 Params: []function.Parameter{ 493 { 494 Name: "list", 495 Type: cty.DynamicPseudoType, 496 }, 497 }, 498 Type: function.StaticReturnType(cty.Number), 499 RefineResult: refineNotNull, 500 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 501 502 if !args[0].CanIterateElements() { 503 return cty.NilVal, function.NewArgErrorf(0, "cannot sum noniterable") 504 } 505 506 if args[0].LengthInt() == 0 { // Easy path 507 return cty.NilVal, function.NewArgErrorf(0, "cannot sum an empty list") 508 } 509 510 arg := args[0].AsValueSlice() 511 ty := args[0].Type() 512 513 if !ty.IsListType() && !ty.IsSetType() && !ty.IsTupleType() { 514 return cty.NilVal, function.NewArgErrorf(0, fmt.Sprintf("argument must be list, set, or tuple. Received %s", ty.FriendlyName())) 515 } 516 517 if !args[0].IsWhollyKnown() { 518 return cty.UnknownVal(cty.Number), nil 519 } 520 521 // big.Float.Add can panic if the input values are opposing infinities, 522 // so we must catch that here in order to remain within 523 // the cty Function abstraction. 524 defer func() { 525 if r := recover(); r != nil { 526 if _, ok := r.(big.ErrNaN); ok { 527 ret = cty.NilVal 528 err = fmt.Errorf("can't compute sum of opposing infinities") 529 } else { 530 // not a panic we recognize 531 panic(r) 532 } 533 } 534 }() 535 536 s := arg[0] 537 if s.IsNull() { 538 return cty.NilVal, function.NewArgErrorf(0, "argument must be list, set, or tuple of number values") 539 } 540 s, err = convert.Convert(s, cty.Number) 541 if err != nil { 542 return cty.NilVal, function.NewArgErrorf(0, "argument must be list, set, or tuple of number values") 543 } 544 for _, v := range arg[1:] { 545 if v.IsNull() { 546 return cty.NilVal, function.NewArgErrorf(0, "argument must be list, set, or tuple of number values") 547 } 548 v, err = convert.Convert(v, cty.Number) 549 if err != nil { 550 return cty.NilVal, function.NewArgErrorf(0, "argument must be list, set, or tuple of number values") 551 } 552 s = s.Add(v) 553 } 554 555 return s, nil 556 }, 557 }) 558 559 // TransposeFunc constructs a function that takes a map of lists of strings and 560 // swaps the keys and values to produce a new map of lists of strings. 561 var TransposeFunc = function.New(&function.Spec{ 562 Params: []function.Parameter{ 563 { 564 Name: "values", 565 Type: cty.Map(cty.List(cty.String)), 566 }, 567 }, 568 Type: function.StaticReturnType(cty.Map(cty.List(cty.String))), 569 RefineResult: refineNotNull, 570 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 571 inputMap := args[0] 572 if !inputMap.IsWhollyKnown() { 573 return cty.UnknownVal(retType), nil 574 } 575 576 outputMap := make(map[string]cty.Value) 577 tmpMap := make(map[string][]string) 578 579 for it := inputMap.ElementIterator(); it.Next(); { 580 inKey, inVal := it.Element() 581 for iter := inVal.ElementIterator(); iter.Next(); { 582 _, val := iter.Element() 583 if !val.Type().Equals(cty.String) { 584 return cty.MapValEmpty(cty.List(cty.String)), errors.New("input must be a map of lists of strings") 585 } 586 587 outKey := val.AsString() 588 if _, ok := tmpMap[outKey]; !ok { 589 tmpMap[outKey] = make([]string, 0) 590 } 591 outVal := tmpMap[outKey] 592 outVal = append(outVal, inKey.AsString()) 593 sort.Strings(outVal) 594 tmpMap[outKey] = outVal 595 } 596 } 597 598 for outKey, outVal := range tmpMap { 599 values := make([]cty.Value, 0) 600 for _, v := range outVal { 601 values = append(values, cty.StringVal(v)) 602 } 603 outputMap[outKey] = cty.ListVal(values) 604 } 605 606 if len(outputMap) == 0 { 607 return cty.MapValEmpty(cty.List(cty.String)), nil 608 } 609 610 return cty.MapVal(outputMap), nil 611 }, 612 }) 613 614 // ListFunc constructs a function that takes an arbitrary number of arguments 615 // and returns a list containing those values in the same order. 616 // 617 // This function is deprecated in Terraform v0.12 618 var ListFunc = function.New(&function.Spec{ 619 Params: []function.Parameter{}, 620 VarParam: &function.Parameter{ 621 Name: "vals", 622 Type: cty.DynamicPseudoType, 623 AllowUnknown: true, 624 AllowDynamicType: true, 625 AllowNull: true, 626 }, 627 Type: func(args []cty.Value) (ret cty.Type, err error) { 628 return cty.DynamicPseudoType, fmt.Errorf("the \"list\" function was deprecated in Terraform v0.12 and is no longer available; use tolist([ ... ]) syntax to write a literal list") 629 }, 630 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 631 return cty.DynamicVal, fmt.Errorf("the \"list\" function was deprecated in Terraform v0.12 and is no longer available; use tolist([ ... ]) syntax to write a literal list") 632 }, 633 }) 634 635 // MapFunc constructs a function that takes an even number of arguments and 636 // returns a map whose elements are constructed from consecutive pairs of arguments. 637 // 638 // This function is deprecated in Terraform v0.12 639 var MapFunc = function.New(&function.Spec{ 640 Params: []function.Parameter{}, 641 VarParam: &function.Parameter{ 642 Name: "vals", 643 Type: cty.DynamicPseudoType, 644 AllowUnknown: true, 645 AllowDynamicType: true, 646 AllowNull: true, 647 }, 648 Type: func(args []cty.Value) (ret cty.Type, err error) { 649 return cty.DynamicPseudoType, fmt.Errorf("the \"map\" function was deprecated in Terraform v0.12 and is no longer available; use tomap({ ... }) syntax to write a literal map") 650 }, 651 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 652 return cty.DynamicVal, fmt.Errorf("the \"map\" function was deprecated in Terraform v0.12 and is no longer available; use tomap({ ... }) syntax to write a literal map") 653 }, 654 }) 655 656 // Length returns the number of elements in the given collection or number of 657 // Unicode characters in the given string. 658 func Length(collection cty.Value) (cty.Value, error) { 659 return LengthFunc.Call([]cty.Value{collection}) 660 } 661 662 // AllTrue returns true if all elements of the list are true. If the list is empty, 663 // return true. 664 func AllTrue(collection cty.Value) (cty.Value, error) { 665 return AllTrueFunc.Call([]cty.Value{collection}) 666 } 667 668 // AnyTrue returns true if any element of the list is true. If the list is empty, 669 // return false. 670 func AnyTrue(collection cty.Value) (cty.Value, error) { 671 return AnyTrueFunc.Call([]cty.Value{collection}) 672 } 673 674 // Coalesce takes any number of arguments and returns the first one that isn't empty. 675 func Coalesce(args ...cty.Value) (cty.Value, error) { 676 return CoalesceFunc.Call(args) 677 } 678 679 // Index finds the element index for a given value in a list. 680 func Index(list, value cty.Value) (cty.Value, error) { 681 return IndexFunc.Call([]cty.Value{list, value}) 682 } 683 684 // List takes any number of list arguments and returns a list containing those 685 // 686 // values in the same order. 687 func List(args ...cty.Value) (cty.Value, error) { 688 return ListFunc.Call(args) 689 } 690 691 // Lookup performs a dynamic lookup into a map. 692 // There are two required arguments, map and key, plus an optional default, 693 // which is a value to return if no key is found in map. 694 func Lookup(args ...cty.Value) (cty.Value, error) { 695 return LookupFunc.Call(args) 696 } 697 698 // Map takes an even number of arguments and returns a map whose elements are constructed 699 // from consecutive pairs of arguments. 700 func Map(args ...cty.Value) (cty.Value, error) { 701 return MapFunc.Call(args) 702 } 703 704 // Matchkeys constructs a new list by taking a subset of elements from one list 705 // whose indexes match the corresponding indexes of values in another list. 706 func Matchkeys(values, keys, searchset cty.Value) (cty.Value, error) { 707 return MatchkeysFunc.Call([]cty.Value{values, keys, searchset}) 708 } 709 710 // One returns either the first element of a one-element list, or null 711 // if given a zero-element list.. 712 func One(list cty.Value) (cty.Value, error) { 713 return OneFunc.Call([]cty.Value{list}) 714 } 715 716 // Sum adds numbers in a list, set, or tuple 717 func Sum(list cty.Value) (cty.Value, error) { 718 return SumFunc.Call([]cty.Value{list}) 719 } 720 721 // Transpose takes a map of lists of strings and swaps the keys and values to 722 // produce a new map of lists of strings. 723 func Transpose(values cty.Value) (cty.Value, error) { 724 return TransposeFunc.Call([]cty.Value{values}) 725 }