diff --git a/.changes/unreleased/ENHANCEMENTS-20240423-165354.yaml b/.changes/unreleased/ENHANCEMENTS-20240423-165354.yaml new file mode 100644 index 000000000..125ce166a --- /dev/null +++ b/.changes/unreleased/ENHANCEMENTS-20240423-165354.yaml @@ -0,0 +1,6 @@ +kind: ENHANCEMENTS +body: 'function: Introduced implementation errors for collection and object parameters + and returns which are missing type information' +time: 2024-04-23T16:53:54.509459-04:00 +custom: + Issue: "991" diff --git a/function/bool_parameter.go b/function/bool_parameter.go index 67929c31f..7cc96b501 100644 --- a/function/bool_parameter.go +++ b/function/bool_parameter.go @@ -4,13 +4,17 @@ package function import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) // Ensure the implementation satisifies the desired interfaces. var _ Parameter = BoolParameter{} var _ ParameterWithBoolValidators = BoolParameter{} +var _ fwfunction.ParameterWithValidateImplementation = BoolParameter{} // BoolParameter represents a function parameter that is a boolean. // @@ -115,3 +119,9 @@ func (p BoolParameter) GetType() attr.Type { return basetypes.BoolType{} } + +func (p BoolParameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) { + if p.GetName() == "" { + resp.Diagnostics.Append(fwfunction.MissingParameterNameDiag(req.FunctionName, req.ParameterPosition)) + } +} diff --git a/function/bool_parameter_test.go b/function/bool_parameter_test.go index bdcf2a32e..6082fc279 100644 --- a/function/bool_parameter_test.go +++ b/function/bool_parameter_test.go @@ -4,12 +4,15 @@ package function_test import ( + "context" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" "github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes" "github.com/hashicorp/terraform-plugin-framework/internal/testing/testvalidator" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -284,3 +287,58 @@ func TestBoolParameterBoolValidators(t *testing.T) { }) } } + +func TestBoolParameterValidateImplementation(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + param function.BoolParameter + request fwfunction.ValidateParameterImplementationRequest + expected *fwfunction.ValidateParameterImplementationResponse + }{ + "name": { + param: function.BoolParameter{ + Name: "testparam", + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{}, + }, + "name-missing": { + param: function.BoolParameter{ + // Name intentionally missing + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Function \"testfunc\" - Parameter at position 0 does not have a name", + ), + }, + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := &fwfunction.ValidateParameterImplementationResponse{} + testCase.param.ValidateImplementation(context.Background(), testCase.request, got) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/function/definition.go b/function/definition.go index aafb8d02b..ebea48bd1 100644 --- a/function/definition.go +++ b/function/definition.go @@ -81,20 +81,10 @@ func (d Definition) ValidateImplementation(ctx context.Context, req DefinitionVa paramNames := make(map[string]int, len(d.Parameters)) for pos, param := range d.Parameters { parameterPosition := int64(pos) - name := param.GetName() - // If name is not set, add an error diagnostic, parameter names are mandatory. - if name == "" { - diags.AddError( - "Invalid Function Definition", - "When validating the function definition, an implementation issue was found. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("Function %q - Parameter at position %d does not have a name", req.FuncName, pos), - ) - } if paramWithValidateImplementation, ok := param.(fwfunction.ParameterWithValidateImplementation); ok { req := fwfunction.ValidateParameterImplementationRequest{ - Name: name, + FunctionName: req.FuncName, ParameterPosition: ¶meterPosition, } resp := &fwfunction.ValidateParameterImplementationResponse{} @@ -104,7 +94,9 @@ func (d Definition) ValidateImplementation(ctx context.Context, req DefinitionVa diags.Append(resp.Diagnostics...) } + name := param.GetName() conflictPos, exists := paramNames[name] + if exists && name != "" { diags.AddError( "Invalid Function Definition", @@ -120,20 +112,9 @@ func (d Definition) ValidateImplementation(ctx context.Context, req DefinitionVa } if d.VariadicParameter != nil { - name := d.VariadicParameter.GetName() - // If name is not set, add an error diagnostic, parameter names are mandatory. - if name == "" { - diags.AddError( - "Invalid Function Definition", - "When validating the function definition, an implementation issue was found. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("Function %q - The variadic parameter does not have a name", req.FuncName), - ) - } - if paramWithValidateImplementation, ok := d.VariadicParameter.(fwfunction.ParameterWithValidateImplementation); ok { req := fwfunction.ValidateParameterImplementationRequest{ - Name: name, + FunctionName: req.FuncName, } resp := &fwfunction.ValidateParameterImplementationResponse{} @@ -142,7 +123,9 @@ func (d Definition) ValidateImplementation(ctx context.Context, req DefinitionVa diags.Append(resp.Diagnostics...) } + name := d.VariadicParameter.GetName() conflictPos, exists := paramNames[name] + if exists && name != "" { diags.AddError( "Invalid Function Definition", diff --git a/function/dynamic_parameter.go b/function/dynamic_parameter.go index cbf2ea33e..d9303f418 100644 --- a/function/dynamic_parameter.go +++ b/function/dynamic_parameter.go @@ -4,13 +4,17 @@ package function import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) // Ensure the implementation satisifies the desired interfaces. var _ Parameter = DynamicParameter{} var _ ParameterWithDynamicValidators = DynamicParameter{} +var _ fwfunction.ParameterWithValidateImplementation = DynamicParameter{} // DynamicParameter represents a function parameter that is a dynamic, rather // than a static type. Static types are always preferable over dynamic @@ -110,3 +114,9 @@ func (p DynamicParameter) GetType() attr.Type { return basetypes.DynamicType{} } + +func (p DynamicParameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) { + if p.GetName() == "" { + resp.Diagnostics.Append(fwfunction.MissingParameterNameDiag(req.FunctionName, req.ParameterPosition)) + } +} diff --git a/function/dynamic_parameter_test.go b/function/dynamic_parameter_test.go index ccf7ab3f1..d79889c83 100644 --- a/function/dynamic_parameter_test.go +++ b/function/dynamic_parameter_test.go @@ -4,12 +4,15 @@ package function_test import ( + "context" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" "github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes" "github.com/hashicorp/terraform-plugin-framework/internal/testing/testvalidator" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -284,3 +287,58 @@ func TestDynamicParameterDynamicValidators(t *testing.T) { }) } } + +func TestDynamicParameterValidateImplementation(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + param function.DynamicParameter + request fwfunction.ValidateParameterImplementationRequest + expected *fwfunction.ValidateParameterImplementationResponse + }{ + "name": { + param: function.DynamicParameter{ + Name: "testparam", + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{}, + }, + "name-missing": { + param: function.DynamicParameter{ + // Name intentionally missing + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Function \"testfunc\" - Parameter at position 0 does not have a name", + ), + }, + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := &fwfunction.ValidateParameterImplementationResponse{} + testCase.param.ValidateImplementation(context.Background(), testCase.request, got) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/function/float64_parameter.go b/function/float64_parameter.go index 11e31c7ef..54706cec6 100644 --- a/function/float64_parameter.go +++ b/function/float64_parameter.go @@ -4,13 +4,17 @@ package function import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) // Ensure the implementation satisifies the desired interfaces. var _ Parameter = Float64Parameter{} var _ ParameterWithFloat64Validators = Float64Parameter{} +var _ fwfunction.ParameterWithValidateImplementation = Float64Parameter{} // Float64Parameter represents a function parameter that is a 64-bit floating // point number. @@ -112,3 +116,9 @@ func (p Float64Parameter) GetType() attr.Type { return basetypes.Float64Type{} } + +func (p Float64Parameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) { + if p.GetName() == "" { + resp.Diagnostics.Append(fwfunction.MissingParameterNameDiag(req.FunctionName, req.ParameterPosition)) + } +} diff --git a/function/float64_parameter_test.go b/function/float64_parameter_test.go index 1c364d793..1e99b0904 100644 --- a/function/float64_parameter_test.go +++ b/function/float64_parameter_test.go @@ -4,12 +4,15 @@ package function_test import ( + "context" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" "github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes" "github.com/hashicorp/terraform-plugin-framework/internal/testing/testvalidator" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -284,3 +287,58 @@ func TestFloat64ParameterFloat64Validators(t *testing.T) { }) } } + +func TestFloat64ParameterValidateImplementation(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + param function.Float64Parameter + request fwfunction.ValidateParameterImplementationRequest + expected *fwfunction.ValidateParameterImplementationResponse + }{ + "name": { + param: function.Float64Parameter{ + Name: "testparam", + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{}, + }, + "name-missing": { + param: function.Float64Parameter{ + // Name intentionally missing + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Function \"testfunc\" - Parameter at position 0 does not have a name", + ), + }, + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := &fwfunction.ValidateParameterImplementationResponse{} + testCase.param.ValidateImplementation(context.Background(), testCase.request, got) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/function/int64_parameter.go b/function/int64_parameter.go index 15a9700a7..0cdbba43e 100644 --- a/function/int64_parameter.go +++ b/function/int64_parameter.go @@ -4,13 +4,17 @@ package function import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) // Ensure the implementation satisifies the desired interfaces. var _ Parameter = Int64Parameter{} var _ ParameterWithInt64Validators = Int64Parameter{} +var _ fwfunction.ParameterWithValidateImplementation = Int64Parameter{} // Int64Parameter represents a function parameter that is a 64-bit integer. // @@ -111,3 +115,9 @@ func (p Int64Parameter) GetType() attr.Type { return basetypes.Int64Type{} } + +func (p Int64Parameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) { + if p.GetName() == "" { + resp.Diagnostics.Append(fwfunction.MissingParameterNameDiag(req.FunctionName, req.ParameterPosition)) + } +} diff --git a/function/int64_parameter_test.go b/function/int64_parameter_test.go index 21b608bb2..a0880de07 100644 --- a/function/int64_parameter_test.go +++ b/function/int64_parameter_test.go @@ -4,12 +4,15 @@ package function_test import ( + "context" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" "github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes" "github.com/hashicorp/terraform-plugin-framework/internal/testing/testvalidator" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -284,3 +287,58 @@ func TestInt64ParameterInt64Validators(t *testing.T) { }) } } + +func TestInt64ParameterValidateImplementation(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + param function.Int64Parameter + request fwfunction.ValidateParameterImplementationRequest + expected *fwfunction.ValidateParameterImplementationResponse + }{ + "name": { + param: function.Int64Parameter{ + Name: "testparam", + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{}, + }, + "name-missing": { + param: function.Int64Parameter{ + // Name intentionally missing + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Function \"testfunc\" - Parameter at position 0 does not have a name", + ), + }, + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := &fwfunction.ValidateParameterImplementationResponse{} + testCase.param.ValidateImplementation(context.Background(), testCase.request, got) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/function/list_parameter.go b/function/list_parameter.go index cdca5a280..613010b1b 100644 --- a/function/list_parameter.go +++ b/function/list_parameter.go @@ -7,7 +7,6 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -135,14 +134,21 @@ func (p ListParameter) GetType() attr.Type { // errors or panics. This logic runs during the GetProviderSchema RPC and // should never include false positives. func (p ListParameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) { - if p.CustomType == nil && fwtype.ContainsCollectionWithDynamic(p.GetType()) { - var diag diag.Diagnostic - if req.ParameterPosition != nil { - diag = fwtype.ParameterCollectionWithDynamicTypeDiag(*req.ParameterPosition, req.Name) - } else { - diag = fwtype.VariadicParameterCollectionWithDynamicTypeDiag(req.Name) + if p.CustomType == nil { + if fwtype.ContainsCollectionWithDynamic(p.GetType()) { + if req.ParameterPosition != nil { + resp.Diagnostics.Append(fwtype.ParameterCollectionWithDynamicTypeDiag(*req.ParameterPosition, p.GetName())) + } else { + resp.Diagnostics.Append(fwtype.VariadicParameterCollectionWithDynamicTypeDiag(p.GetName())) + } } - resp.Diagnostics.Append(diag) + if fwtype.ContainsMissingUnderlyingType(p.GetType()) { + resp.Diagnostics.Append(fwtype.ParameterMissingUnderlyingTypeDiag(p.GetName(), req.ParameterPosition)) + } + } + + if p.GetName() == "" { + resp.Diagnostics.Append(fwfunction.MissingParameterNameDiag(req.FunctionName, req.ParameterPosition)) } } diff --git a/function/list_parameter_test.go b/function/list_parameter_test.go index 7eb530c41..abca3b288 100644 --- a/function/list_parameter_test.go +++ b/function/list_parameter_test.go @@ -311,6 +311,7 @@ func TestListParameterValidateImplementation(t *testing.T) { }{ "customtype": { param: function.ListParameter{ + Name: "testparam", CustomType: testtypes.ListType{}, }, request: fwfunction.ValidateParameterImplementationRequest{ @@ -320,6 +321,7 @@ func TestListParameterValidateImplementation(t *testing.T) { }, "elementtype": { param: function.ListParameter{ + Name: "testparam", ElementType: types.StringType, }, request: fwfunction.ValidateParameterImplementationRequest{ @@ -333,7 +335,6 @@ func TestListParameterValidateImplementation(t *testing.T) { ElementType: types.DynamicType, }, request: fwfunction.ValidateParameterImplementationRequest{ - Name: "testparam", ParameterPosition: pointer(int64(0)), }, expected: &fwfunction.ValidateParameterImplementationResponse{ @@ -354,9 +355,7 @@ func TestListParameterValidateImplementation(t *testing.T) { Name: "testparam", ElementType: types.DynamicType, }, - request: fwfunction.ValidateParameterImplementationRequest{ - Name: "testparam", - }, + request: fwfunction.ValidateParameterImplementationRequest{}, expected: &fwfunction.ValidateParameterImplementationResponse{ Diagnostics: diag.Diagnostics{ diag.NewErrorDiagnostic( @@ -370,6 +369,57 @@ func TestListParameterValidateImplementation(t *testing.T) { }, }, }, + "elementtype-missing": { + param: function.ListParameter{ + Name: "testparam", + // ElementType intentionally missing + }, + request: fwfunction.ValidateParameterImplementationRequest{ + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Parameter \"testparam\" at position 0 is missing underlying type.\n\n"+ + "Collection element and object attribute types are always required in Terraform.", + ), + }, + }, + }, + "name": { + param: function.ListParameter{ + Name: "testparam", + ElementType: types.StringType, + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{}, + }, + "name-missing": { + param: function.ListParameter{ + // Name intentionally missing + ElementType: types.StringType, + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Function \"testfunc\" - Parameter at position 0 does not have a name", + ), + }, + }, + }, } for name, testCase := range testCases { diff --git a/function/list_return.go b/function/list_return.go index 07eac8ad8..269ce3049 100644 --- a/function/list_return.go +++ b/function/list_return.go @@ -71,7 +71,13 @@ func (r ListReturn) NewResultData(ctx context.Context) (ResultData, *FuncError) // errors or panics. This logic runs during the GetProviderSchema RPC and // should never include false positives. func (p ListReturn) ValidateImplementation(ctx context.Context, req fwfunction.ValidateReturnImplementationRequest, resp *fwfunction.ValidateReturnImplementationResponse) { - if p.CustomType == nil && fwtype.ContainsCollectionWithDynamic(p.GetType()) { - resp.Diagnostics.Append(fwtype.ReturnCollectionWithDynamicTypeDiag()) + if p.CustomType == nil { + if fwtype.ContainsCollectionWithDynamic(p.GetType()) { + resp.Diagnostics.Append(fwtype.ReturnCollectionWithDynamicTypeDiag()) + } + + if fwtype.ContainsMissingUnderlyingType(p.GetType()) { + resp.Diagnostics.Append(fwtype.ReturnMissingUnderlyingTypeDiag()) + } } } diff --git a/function/list_return_test.go b/function/list_return_test.go index 09985c13a..1465597d3 100644 --- a/function/list_return_test.go +++ b/function/list_return_test.go @@ -103,6 +103,23 @@ func TestListReturnValidateImplementation(t *testing.T) { }, }, }, + "elementtype-missing": { + returnDef: function.ListReturn{ + // ElementType intentionally missing + }, + request: fwfunction.ValidateReturnImplementationRequest{}, + expected: &fwfunction.ValidateReturnImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Return is missing underlying type.\n\n"+ + "Collection element and object attribute types are always required in Terraform.", + ), + }, + }, + }, } for name, testCase := range testCases { diff --git a/function/map_parameter.go b/function/map_parameter.go index 626781352..75e6b4f92 100644 --- a/function/map_parameter.go +++ b/function/map_parameter.go @@ -7,7 +7,6 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -135,14 +134,21 @@ func (p MapParameter) GetType() attr.Type { // errors or panics. This logic runs during the GetProviderSchema RPC and // should never include false positives. func (p MapParameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) { - if p.CustomType == nil && fwtype.ContainsCollectionWithDynamic(p.GetType()) { - var diag diag.Diagnostic - if req.ParameterPosition != nil { - diag = fwtype.ParameterCollectionWithDynamicTypeDiag(*req.ParameterPosition, req.Name) - } else { - diag = fwtype.VariadicParameterCollectionWithDynamicTypeDiag(req.Name) + if p.CustomType == nil { + if fwtype.ContainsCollectionWithDynamic(p.GetType()) { + if req.ParameterPosition != nil { + resp.Diagnostics.Append(fwtype.ParameterCollectionWithDynamicTypeDiag(*req.ParameterPosition, p.GetName())) + } else { + resp.Diagnostics.Append(fwtype.VariadicParameterCollectionWithDynamicTypeDiag(p.GetName())) + } } - resp.Diagnostics.Append(diag) + if fwtype.ContainsMissingUnderlyingType(p.GetType()) { + resp.Diagnostics.Append(fwtype.ParameterMissingUnderlyingTypeDiag(p.GetName(), req.ParameterPosition)) + } + } + + if p.GetName() == "" { + resp.Diagnostics.Append(fwfunction.MissingParameterNameDiag(req.FunctionName, req.ParameterPosition)) } } diff --git a/function/map_parameter_test.go b/function/map_parameter_test.go index de8b1dc78..003f03f0b 100644 --- a/function/map_parameter_test.go +++ b/function/map_parameter_test.go @@ -311,6 +311,7 @@ func TestMapParameterValidateImplementation(t *testing.T) { }{ "customtype": { param: function.MapParameter{ + Name: "testparam", CustomType: testtypes.MapType{}, }, request: fwfunction.ValidateParameterImplementationRequest{ @@ -320,6 +321,7 @@ func TestMapParameterValidateImplementation(t *testing.T) { }, "elementtype": { param: function.MapParameter{ + Name: "testparam", ElementType: types.StringType, }, request: fwfunction.ValidateParameterImplementationRequest{ @@ -333,7 +335,6 @@ func TestMapParameterValidateImplementation(t *testing.T) { ElementType: types.DynamicType, }, request: fwfunction.ValidateParameterImplementationRequest{ - Name: "testparam", ParameterPosition: pointer(int64(0)), }, expected: &fwfunction.ValidateParameterImplementationResponse{ @@ -354,9 +355,7 @@ func TestMapParameterValidateImplementation(t *testing.T) { Name: "testparam", ElementType: types.DynamicType, }, - request: fwfunction.ValidateParameterImplementationRequest{ - Name: "testparam", - }, + request: fwfunction.ValidateParameterImplementationRequest{}, expected: &fwfunction.ValidateParameterImplementationResponse{ Diagnostics: diag.Diagnostics{ diag.NewErrorDiagnostic( @@ -370,6 +369,57 @@ func TestMapParameterValidateImplementation(t *testing.T) { }, }, }, + "elementtype-missing": { + param: function.MapParameter{ + Name: "testparam", + // ElementType intentionally missing + }, + request: fwfunction.ValidateParameterImplementationRequest{ + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Parameter \"testparam\" at position 0 is missing underlying type.\n\n"+ + "Collection element and object attribute types are always required in Terraform.", + ), + }, + }, + }, + "name": { + param: function.MapParameter{ + Name: "testparam", + ElementType: types.StringType, + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{}, + }, + "name-missing": { + param: function.MapParameter{ + // Name intentionally missing + ElementType: types.StringType, + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Function \"testfunc\" - Parameter at position 0 does not have a name", + ), + }, + }, + }, } for name, testCase := range testCases { diff --git a/function/map_return.go b/function/map_return.go index 5f83c69c3..3e95aaa32 100644 --- a/function/map_return.go +++ b/function/map_return.go @@ -71,7 +71,13 @@ func (r MapReturn) NewResultData(ctx context.Context) (ResultData, *FuncError) { // errors or panics. This logic runs during the GetProviderSchema RPC and // should never include false positives. func (p MapReturn) ValidateImplementation(ctx context.Context, req fwfunction.ValidateReturnImplementationRequest, resp *fwfunction.ValidateReturnImplementationResponse) { - if p.CustomType == nil && fwtype.ContainsCollectionWithDynamic(p.GetType()) { - resp.Diagnostics.Append(fwtype.ReturnCollectionWithDynamicTypeDiag()) + if p.CustomType == nil { + if fwtype.ContainsCollectionWithDynamic(p.GetType()) { + resp.Diagnostics.Append(fwtype.ReturnCollectionWithDynamicTypeDiag()) + } + + if fwtype.ContainsMissingUnderlyingType(p.GetType()) { + resp.Diagnostics.Append(fwtype.ReturnMissingUnderlyingTypeDiag()) + } } } diff --git a/function/map_return_test.go b/function/map_return_test.go index 1e30f538c..b919231c2 100644 --- a/function/map_return_test.go +++ b/function/map_return_test.go @@ -103,6 +103,23 @@ func TestMapReturnValidateImplementation(t *testing.T) { }, }, }, + "elementtype-missing": { + returnDef: function.MapReturn{ + // ElementType intentionally missing + }, + request: fwfunction.ValidateReturnImplementationRequest{}, + expected: &fwfunction.ValidateReturnImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Return is missing underlying type.\n\n"+ + "Collection element and object attribute types are always required in Terraform.", + ), + }, + }, + }, } for name, testCase := range testCases { diff --git a/function/number_parameter.go b/function/number_parameter.go index 1114f2354..4cd4f9be3 100644 --- a/function/number_parameter.go +++ b/function/number_parameter.go @@ -4,13 +4,17 @@ package function import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) // Ensure the implementation satisifies the desired interfaces. var _ Parameter = NumberParameter{} var _ ParameterWithNumberValidators = NumberParameter{} +var _ fwfunction.ParameterWithValidateImplementation = NumberParameter{} // NumberParameter represents a function parameter that is a 512-bit arbitrary // precision number. @@ -110,3 +114,9 @@ func (p NumberParameter) GetType() attr.Type { return basetypes.NumberType{} } + +func (p NumberParameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) { + if p.GetName() == "" { + resp.Diagnostics.Append(fwfunction.MissingParameterNameDiag(req.FunctionName, req.ParameterPosition)) + } +} diff --git a/function/number_parameter_test.go b/function/number_parameter_test.go index d9b11bbb3..d5fbc23f1 100644 --- a/function/number_parameter_test.go +++ b/function/number_parameter_test.go @@ -4,12 +4,15 @@ package function_test import ( + "context" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" "github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes" "github.com/hashicorp/terraform-plugin-framework/internal/testing/testvalidator" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -284,3 +287,58 @@ func TestNumberParameterNumberValidators(t *testing.T) { }) } } + +func TestNumberParameterValidateImplementation(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + param function.NumberParameter + request fwfunction.ValidateParameterImplementationRequest + expected *fwfunction.ValidateParameterImplementationResponse + }{ + "name": { + param: function.NumberParameter{ + Name: "testparam", + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{}, + }, + "name-missing": { + param: function.NumberParameter{ + // Name intentionally missing + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Function \"testfunc\" - Parameter at position 0 does not have a name", + ), + }, + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := &fwfunction.ValidateParameterImplementationResponse{} + testCase.param.ValidateImplementation(context.Background(), testCase.request, got) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/function/object_parameter.go b/function/object_parameter.go index 13120c144..a36ed87df 100644 --- a/function/object_parameter.go +++ b/function/object_parameter.go @@ -7,7 +7,6 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -137,14 +136,21 @@ func (p ObjectParameter) GetType() attr.Type { // errors or panics. This logic runs during the GetProviderSchema RPC and // should never include false positives. func (p ObjectParameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) { - if p.CustomType == nil && fwtype.ContainsCollectionWithDynamic(p.GetType()) { - var diag diag.Diagnostic - if req.ParameterPosition != nil { - diag = fwtype.ParameterCollectionWithDynamicTypeDiag(*req.ParameterPosition, req.Name) - } else { - diag = fwtype.VariadicParameterCollectionWithDynamicTypeDiag(req.Name) + if p.CustomType == nil { + if fwtype.ContainsCollectionWithDynamic(p.GetType()) { + if req.ParameterPosition != nil { + resp.Diagnostics.Append(fwtype.ParameterCollectionWithDynamicTypeDiag(*req.ParameterPosition, p.GetName())) + } else { + resp.Diagnostics.Append(fwtype.VariadicParameterCollectionWithDynamicTypeDiag(p.GetName())) + } } - resp.Diagnostics.Append(diag) + if fwtype.ContainsMissingUnderlyingType(p.GetType()) { + resp.Diagnostics.Append(fwtype.ParameterMissingUnderlyingTypeDiag(p.GetName(), req.ParameterPosition)) + } + } + + if p.GetName() == "" { + resp.Diagnostics.Append(fwfunction.MissingParameterNameDiag(req.FunctionName, req.ParameterPosition)) } } diff --git a/function/object_parameter_test.go b/function/object_parameter_test.go index ef42e56b3..79c86e234 100644 --- a/function/object_parameter_test.go +++ b/function/object_parameter_test.go @@ -319,6 +319,7 @@ func TestObjectParameterValidateImplementation(t *testing.T) { }{ "customtype": { param: function.ObjectParameter{ + Name: "testparam", CustomType: testtypes.ObjectType{}, }, request: fwfunction.ValidateParameterImplementationRequest{ @@ -328,6 +329,7 @@ func TestObjectParameterValidateImplementation(t *testing.T) { }, "attributetypes": { param: function.ObjectParameter{ + Name: "testparam", AttributeTypes: map[string]attr.Type{ "test_attr": types.StringType, }, @@ -339,6 +341,7 @@ func TestObjectParameterValidateImplementation(t *testing.T) { }, "attributetypes-dynamic": { param: function.ObjectParameter{ + Name: "testparam", AttributeTypes: map[string]attr.Type{ "test_attr": types.DynamicType, "test_list": types.ListType{ @@ -366,7 +369,6 @@ func TestObjectParameterValidateImplementation(t *testing.T) { }, }, request: fwfunction.ValidateParameterImplementationRequest{ - Name: "testparam", ParameterPosition: pointer(int64(0)), }, expected: &fwfunction.ValidateParameterImplementationResponse{ @@ -391,9 +393,7 @@ func TestObjectParameterValidateImplementation(t *testing.T) { }, }, }, - request: fwfunction.ValidateParameterImplementationRequest{ - Name: "testparam", - }, + request: fwfunction.ValidateParameterImplementationRequest{}, expected: &fwfunction.ValidateParameterImplementationResponse{ Diagnostics: diag.Diagnostics{ diag.NewErrorDiagnostic( @@ -407,6 +407,75 @@ func TestObjectParameterValidateImplementation(t *testing.T) { }, }, }, + "attributetypes-missing": { + param: function.ObjectParameter{ + Name: "testparam", + // AttributeTypes intentionally missing + }, + request: fwfunction.ValidateParameterImplementationRequest{ + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{ + // No diagnostics are expected as objects can be empty + }, + }, + "attributetypes-missing-underlying-type": { + param: function.ObjectParameter{ + Name: "testparam", + AttributeTypes: map[string]attr.Type{ + "nil": nil, + }, + }, + request: fwfunction.ValidateParameterImplementationRequest{ + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Parameter \"testparam\" at position 0 is missing underlying type.\n\n"+ + "Collection element and object attribute types are always required in Terraform.", + ), + }, + }, + }, + "name": { + param: function.ObjectParameter{ + Name: "testparam", + AttributeTypes: map[string]attr.Type{ + "test_attr": types.StringType, + }, + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{}, + }, + "name-missing": { + param: function.ObjectParameter{ + // Name intentionally missing + AttributeTypes: map[string]attr.Type{ + "test_attr": types.StringType, + }, + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Function \"testfunc\" - Parameter at position 0 does not have a name", + ), + }, + }, + }, } for name, testCase := range testCases { diff --git a/function/object_return.go b/function/object_return.go index 201960f95..a4afb08d2 100644 --- a/function/object_return.go +++ b/function/object_return.go @@ -67,7 +67,13 @@ func (r ObjectReturn) NewResultData(ctx context.Context) (ResultData, *FuncError // errors or panics. This logic runs during the GetProviderSchema RPC and // should never include false positives. func (p ObjectReturn) ValidateImplementation(ctx context.Context, req fwfunction.ValidateReturnImplementationRequest, resp *fwfunction.ValidateReturnImplementationResponse) { - if p.CustomType == nil && fwtype.ContainsCollectionWithDynamic(p.GetType()) { - resp.Diagnostics.Append(fwtype.ReturnCollectionWithDynamicTypeDiag()) + if p.CustomType == nil { + if fwtype.ContainsCollectionWithDynamic(p.GetType()) { + resp.Diagnostics.Append(fwtype.ReturnCollectionWithDynamicTypeDiag()) + } + + if fwtype.ContainsMissingUnderlyingType(p.GetType()) { + resp.Diagnostics.Append(fwtype.ReturnMissingUnderlyingTypeDiag()) + } } } diff --git a/function/object_return_test.go b/function/object_return_test.go index 12699b7f6..af1c78652 100644 --- a/function/object_return_test.go +++ b/function/object_return_test.go @@ -134,6 +134,34 @@ func TestObjectReturnValidateImplementation(t *testing.T) { }, }, }, + "attributetypes-missing": { + returnDef: function.ObjectReturn{ + // AttributeTypes intentionally missing + }, + request: fwfunction.ValidateReturnImplementationRequest{}, + expected: &fwfunction.ValidateReturnImplementationResponse{ + // No diagnostics are expected as objects can be empty + }, + }, + "attributetypes-missing-underlying-type": { + returnDef: function.ObjectReturn{ + AttributeTypes: map[string]attr.Type{ + "nil": nil, + }, + }, + request: fwfunction.ValidateReturnImplementationRequest{}, + expected: &fwfunction.ValidateReturnImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Return is missing underlying type.\n\n"+ + "Collection element and object attribute types are always required in Terraform.", + ), + }, + }, + }, } for name, testCase := range testCases { diff --git a/function/set_parameter.go b/function/set_parameter.go index 16a0c312b..625c7c166 100644 --- a/function/set_parameter.go +++ b/function/set_parameter.go @@ -7,7 +7,6 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -135,14 +134,21 @@ func (p SetParameter) GetType() attr.Type { // errors or panics. This logic runs during the GetProviderSchema RPC and // should never include false positives. func (p SetParameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) { - if p.CustomType == nil && fwtype.ContainsCollectionWithDynamic(p.GetType()) { - var diag diag.Diagnostic - if req.ParameterPosition != nil { - diag = fwtype.ParameterCollectionWithDynamicTypeDiag(*req.ParameterPosition, req.Name) - } else { - diag = fwtype.VariadicParameterCollectionWithDynamicTypeDiag(req.Name) + if p.CustomType == nil { + if fwtype.ContainsCollectionWithDynamic(p.GetType()) { + if req.ParameterPosition != nil { + resp.Diagnostics.Append(fwtype.ParameterCollectionWithDynamicTypeDiag(*req.ParameterPosition, p.GetName())) + } else { + resp.Diagnostics.Append(fwtype.VariadicParameterCollectionWithDynamicTypeDiag(p.GetName())) + } } - resp.Diagnostics.Append(diag) + if fwtype.ContainsMissingUnderlyingType(p.GetType()) { + resp.Diagnostics.Append(fwtype.ParameterMissingUnderlyingTypeDiag(p.GetName(), req.ParameterPosition)) + } + } + + if p.GetName() == "" { + resp.Diagnostics.Append(fwfunction.MissingParameterNameDiag(req.FunctionName, req.ParameterPosition)) } } diff --git a/function/set_parameter_test.go b/function/set_parameter_test.go index 1b7f43499..705640dbb 100644 --- a/function/set_parameter_test.go +++ b/function/set_parameter_test.go @@ -311,6 +311,7 @@ func TestSetParameterValidateImplementation(t *testing.T) { }{ "customtype": { param: function.SetParameter{ + Name: "testparam", CustomType: testtypes.SetType{}, }, request: fwfunction.ValidateParameterImplementationRequest{ @@ -320,6 +321,7 @@ func TestSetParameterValidateImplementation(t *testing.T) { }, "elementtype": { param: function.SetParameter{ + Name: "testparam", ElementType: types.StringType, }, request: fwfunction.ValidateParameterImplementationRequest{ @@ -333,7 +335,6 @@ func TestSetParameterValidateImplementation(t *testing.T) { ElementType: types.DynamicType, }, request: fwfunction.ValidateParameterImplementationRequest{ - Name: "testparam", ParameterPosition: pointer(int64(0)), }, expected: &fwfunction.ValidateParameterImplementationResponse{ @@ -354,9 +355,7 @@ func TestSetParameterValidateImplementation(t *testing.T) { Name: "testparam", ElementType: types.DynamicType, }, - request: fwfunction.ValidateParameterImplementationRequest{ - Name: "testparam", - }, + request: fwfunction.ValidateParameterImplementationRequest{}, expected: &fwfunction.ValidateParameterImplementationResponse{ Diagnostics: diag.Diagnostics{ diag.NewErrorDiagnostic( @@ -370,6 +369,57 @@ func TestSetParameterValidateImplementation(t *testing.T) { }, }, }, + "elementtype-missing": { + param: function.SetParameter{ + Name: "testparam", + // ElementType intentionally missing + }, + request: fwfunction.ValidateParameterImplementationRequest{ + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Parameter \"testparam\" at position 0 is missing underlying type.\n\n"+ + "Collection element and object attribute types are always required in Terraform.", + ), + }, + }, + }, + "name": { + param: function.SetParameter{ + Name: "testparam", + ElementType: types.StringType, + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{}, + }, + "name-missing": { + param: function.SetParameter{ + // Name intentionally missing + ElementType: types.StringType, + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Function \"testfunc\" - Parameter at position 0 does not have a name", + ), + }, + }, + }, } for name, testCase := range testCases { diff --git a/function/set_return.go b/function/set_return.go index 2999a4067..355538b65 100644 --- a/function/set_return.go +++ b/function/set_return.go @@ -71,7 +71,13 @@ func (r SetReturn) NewResultData(ctx context.Context) (ResultData, *FuncError) { // errors or panics. This logic runs during the GetProviderSchema RPC and // should never include false positives. func (p SetReturn) ValidateImplementation(ctx context.Context, req fwfunction.ValidateReturnImplementationRequest, resp *fwfunction.ValidateReturnImplementationResponse) { - if p.CustomType == nil && fwtype.ContainsCollectionWithDynamic(p.GetType()) { - resp.Diagnostics.Append(fwtype.ReturnCollectionWithDynamicTypeDiag()) + if p.CustomType == nil { + if fwtype.ContainsCollectionWithDynamic(p.GetType()) { + resp.Diagnostics.Append(fwtype.ReturnCollectionWithDynamicTypeDiag()) + } + + if fwtype.ContainsMissingUnderlyingType(p.GetType()) { + resp.Diagnostics.Append(fwtype.ReturnMissingUnderlyingTypeDiag()) + } } } diff --git a/function/set_return_test.go b/function/set_return_test.go index a6452c071..ef973f3f6 100644 --- a/function/set_return_test.go +++ b/function/set_return_test.go @@ -103,6 +103,23 @@ func TestSetReturnValidateImplementation(t *testing.T) { }, }, }, + "elementtype-missing": { + returnDef: function.SetReturn{ + // ElementType intentionally missing + }, + request: fwfunction.ValidateReturnImplementationRequest{}, + expected: &fwfunction.ValidateReturnImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Return is missing underlying type.\n\n"+ + "Collection element and object attribute types are always required in Terraform.", + ), + }, + }, + }, } for name, testCase := range testCases { diff --git a/function/string_parameter.go b/function/string_parameter.go index 6e6bfe10b..0035a6204 100644 --- a/function/string_parameter.go +++ b/function/string_parameter.go @@ -4,13 +4,17 @@ package function import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) // Ensure the implementation satisifies the desired interfaces. var _ Parameter = StringParameter{} var _ ParameterWithStringValidators = StringParameter{} +var _ fwfunction.ParameterWithValidateImplementation = StringParameter{} // StringParameter represents a function parameter that is a string. // @@ -111,3 +115,9 @@ func (p StringParameter) GetType() attr.Type { return basetypes.StringType{} } + +func (p StringParameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) { + if p.GetName() == "" { + resp.Diagnostics.Append(fwfunction.MissingParameterNameDiag(req.FunctionName, req.ParameterPosition)) + } +} diff --git a/function/string_parameter_test.go b/function/string_parameter_test.go index 772695ec4..73a8a18cb 100644 --- a/function/string_parameter_test.go +++ b/function/string_parameter_test.go @@ -4,12 +4,15 @@ package function_test import ( + "context" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" "github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes" "github.com/hashicorp/terraform-plugin-framework/internal/testing/testvalidator" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -284,3 +287,58 @@ func TestStringParameterStringValidators(t *testing.T) { }) } } + +func TestStringParameterValidateImplementation(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + param function.StringParameter + request fwfunction.ValidateParameterImplementationRequest + expected *fwfunction.ValidateParameterImplementationResponse + }{ + "name": { + param: function.StringParameter{ + Name: "testparam", + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{}, + }, + "name-missing": { + param: function.StringParameter{ + // Name intentionally missing + }, + request: fwfunction.ValidateParameterImplementationRequest{ + FunctionName: "testfunc", + ParameterPosition: pointer(int64(0)), + }, + expected: &fwfunction.ValidateParameterImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Function \"testfunc\" - Parameter at position 0 does not have a name", + ), + }, + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := &fwfunction.ValidateParameterImplementationResponse{} + testCase.param.ValidateImplementation(context.Background(), testCase.request, got) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/internal/fwfunction/diagnostics.go b/internal/fwfunction/diagnostics.go new file mode 100644 index 000000000..86e391cba --- /dev/null +++ b/internal/fwfunction/diagnostics.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fwfunction + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" +) + +func MissingParameterNameDiag(functionName string, position *int64) diag.Diagnostic { + if position == nil { + return diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Function %q - The variadic parameter does not have a name", functionName), + ) + } + + return diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Function %q - Parameter at position %d does not have a name", functionName, *position), + ) +} diff --git a/internal/fwfunction/parameter_validate_implementation.go b/internal/fwfunction/parameter_validate_implementation.go index 1e171f016..050193f34 100644 --- a/internal/fwfunction/parameter_validate_implementation.go +++ b/internal/fwfunction/parameter_validate_implementation.go @@ -30,16 +30,12 @@ type ParameterWithValidateImplementation interface { // definition. ValidateParameterImplementationResponse is the type used for // responses. type ValidateParameterImplementationRequest struct { + // FunctionName is the name of the function being validated. + FunctionName string + // ParameterPosition is the position of the parameter in the function definition for reporting diagnostics. // A parameter without a position (i.e. `nil`) is the variadic parameter. ParameterPosition *int64 - - // Name is the provider-defined parameter name or the default parameter name for reporting diagnostics. - // - // MAINTAINER NOTE: Since parameter names are not required currently and can be defaulted by internal framework logic, - // we accept the Name in this validate request, rather than using `(function.Parameter).GetName()` for diagnostics, which - // could be empty. - Name string } // ValidateParameterImplementationResponse contains the returned data from a diff --git a/internal/fwtype/missing_underlying_type_validation.go b/internal/fwtype/missing_underlying_type_validation.go new file mode 100644 index 000000000..9d048113b --- /dev/null +++ b/internal/fwtype/missing_underlying_type_validation.go @@ -0,0 +1,81 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fwtype + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// ContainsMissingUnderlyingType will return true if an attr.Type is +// a complex type that either is or contains any collection types with missing +// element or attribute types. Primitives will return false. Nil will return +// true. +func ContainsMissingUnderlyingType(typ attr.Type) bool { + // The below logic must use AttrTypes/ElemType/ElemTypes directly, or the + // types package will return the unexported missingType type, which cannot + // be caught here. + switch attrType := typ.(type) { + case nil: + return true + case basetypes.ListType: + return ContainsMissingUnderlyingType(attrType.ElemType) + case basetypes.MapType: + return ContainsMissingUnderlyingType(attrType.ElemType) + case basetypes.ObjectType: + for _, objAttrType := range attrType.AttrTypes { + if ContainsMissingUnderlyingType(objAttrType) { + return true + } + } + + return false + case basetypes.SetType: + return ContainsMissingUnderlyingType(attrType.ElemType) + case basetypes.TupleType: + for _, elemType := range attrType.ElemTypes { + if ContainsMissingUnderlyingType(elemType) { + return true + } + } + + return false + // Everything else (primitives, custom types, etc.) + default: + return false + } +} + +func ParameterMissingUnderlyingTypeDiag(name string, position *int64) diag.Diagnostic { + if position == nil { + return diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Variadic parameter is missing underlying type.\n\n"+ + "Collection element and object attribute types are always required in Terraform.", + ) + } + + return diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Parameter %q at position %d is missing underlying type.\n\n", name, *position)+ + "Collection element and object attribute types are always required in Terraform.", + ) +} + +func ReturnMissingUnderlyingTypeDiag() diag.Diagnostic { + return diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Return is missing underlying type.\n\n"+ + "Collection element and object attribute types are always required in Terraform.", + ) +} diff --git a/internal/fwtype/missing_underlying_type_validation_test.go b/internal/fwtype/missing_underlying_type_validation_test.go new file mode 100644 index 000000000..99931d2b8 --- /dev/null +++ b/internal/fwtype/missing_underlying_type_validation_test.go @@ -0,0 +1,2726 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fwtype_test + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func TestContainsMissingUnderlyingType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attrTyp attr.Type + expected bool + }{ + "nil": { + attrTyp: nil, + expected: true, + }, + "bool": { + attrTyp: types.BoolType, + expected: false, + }, + "custom-bool": { + attrTyp: testtypes.BoolType{}, + expected: false, + }, + "custom-dynamic": { + attrTyp: testtypes.DynamicType{}, + expected: false, + }, + "custom-float64": { + attrTyp: testtypes.Float64Type{}, + expected: false, + }, + "custom-int64": { + attrTyp: testtypes.Int64Type{}, + expected: false, + }, + "custom-list-nil": { + attrTyp: testtypes.ListType{}, + // While testtypes.ListType embeds basetypes.ListType and this test + // case does not specify an ElemType value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // ElementType() method which would be used for custom types. + expected: false, + }, + "custom-map-nil": { + attrTyp: testtypes.MapType{}, + // While testtypes.MapType embeds basetypes.MapType and this test + // case does not specify an ElemType value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // ElementType() method which would be used for custom types. + expected: false, + }, + "custom-object-nil": { + attrTyp: testtypes.ObjectType{}, + expected: false, // expected as objects can be empty + }, + "custom-number": { + attrTyp: testtypes.NumberType{}, + expected: false, + }, + "custom-set-nil": { + attrTyp: testtypes.SetType{}, + // While testtypes.SetType embeds basetypes.SetType and this test + // case does not specify an ElemType value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // ElementType() method which would be used for custom types. + expected: false, + }, + "custom-string": { + attrTyp: testtypes.StringType{}, + expected: false, + }, + "dynamic": { + attrTyp: types.DynamicType, + expected: false, + }, + "float64": { + attrTyp: types.Float64Type, + expected: false, + }, + "int64": { + attrTyp: types.Float64Type, + expected: false, + }, + "list-nil": { + attrTyp: types.ListType{}, + expected: true, + }, + "list-bool": { + attrTyp: types.ListType{ + ElemType: types.BoolType, + }, + expected: false, + }, + "list-custom-bool": { + attrTyp: types.ListType{ + ElemType: testtypes.BoolType{}, + }, + expected: false, + }, + "list-custom-dynamic": { + attrTyp: types.ListType{ + ElemType: testtypes.DynamicType{}, + }, + expected: false, + }, + "list-custom-float64": { + attrTyp: types.ListType{ + ElemType: testtypes.Float64Type{}, + }, + expected: false, + }, + "list-custom-int64": { + attrTyp: types.ListType{ + ElemType: testtypes.Int64Type{}, + }, + expected: false, + }, + "list-custom-list-nil": { + attrTyp: types.ListType{ + ElemType: testtypes.ListType{}, + }, + // While testtypes.ListType embeds basetypes.ListType and this test + // case does not specify an ElemType value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // ElementType() method which would be used for custom types. + expected: false, + }, + "list-custom-list-string": { + attrTyp: types.ListType{ + ElemType: testtypes.ListType{ + ListType: types.ListType{ + ElemType: types.StringType, + }, + }, + }, + expected: false, + }, + "list-custom-map-nil": { + attrTyp: types.ListType{ + ElemType: testtypes.MapType{}, + }, + // While testtypes.MapType embeds basetypes.MapType and this test + // case does not specify an ElemType value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // ElementType() method which would be used for custom types. + expected: false, + }, + "list-custom-map-string": { + attrTyp: types.ListType{ + ElemType: testtypes.MapType{ + MapType: types.MapType{ + ElemType: types.StringType, + }, + }, + }, + expected: false, + }, + "list-custom-number": { + attrTyp: types.ListType{ + ElemType: testtypes.NumberType{}, + }, + expected: false, + }, + "list-custom-object-nil": { + attrTyp: types.ListType{ + ElemType: testtypes.ObjectType{}, + }, + expected: false, // expected as objects can be empty + }, + "list-custom-object-attrtypes": { + attrTyp: types.ListType{ + ElemType: testtypes.ObjectType{ + ObjectType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + }, + expected: false, + }, + "list-custom-object-attrtypes-nil": { + attrTyp: types.ListType{ + ElemType: testtypes.ObjectType{ + ObjectType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": nil, + }, + }, + }, + }, + // While testtypes.ObjectType embeds basetypes.ObjectType and this + // test case specifies a nil AttrTypes value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // AttributeTypes() method which would be used for custom types. + expected: false, + }, + "list-custom-set-nil": { + attrTyp: types.ListType{ + ElemType: testtypes.SetType{}, + }, + // While testtypes.SetType embeds basetypes.SetType and this test + // case does not specify an ElemType value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // ElementType() method which would be used for custom types. + expected: false, + }, + "list-custom-set-string": { + attrTyp: types.ListType{ + ElemType: testtypes.SetType{ + SetType: types.SetType{ + ElemType: types.StringType, + }, + }, + }, + expected: false, + }, + "list-custom-string": { + attrTyp: types.ListType{ + ElemType: testtypes.StringType{}, + }, + expected: false, + }, + "list-dynamic": { + attrTyp: types.ListType{ + ElemType: types.DynamicType, + }, + expected: false, + }, + "list-float64": { + attrTyp: types.ListType{ + ElemType: types.Float64Type, + }, + expected: false, + }, + "list-int64": { + attrTyp: types.ListType{ + ElemType: types.Int64Type, + }, + expected: false, + }, + "list-list-nil": { + attrTyp: types.ListType{ + ElemType: types.ListType{}, + }, + expected: true, + }, + "list-list-string": { + attrTyp: types.ListType{ + ElemType: types.ListType{ + ElemType: types.StringType, + }, + }, + expected: false, + }, + "list-map-nil": { + attrTyp: types.ListType{ + ElemType: types.MapType{}, + }, + expected: true, + }, + "list-map-string": { + attrTyp: types.ListType{ + ElemType: types.MapType{ + ElemType: types.StringType, + }, + }, + expected: false, + }, + "list-object-nil": { + attrTyp: types.ListType{ + ElemType: types.ObjectType{}, + }, + expected: false, // expected as objects can be empty + }, + "list-object-attrtypes": { + attrTyp: types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + expected: false, + }, + "list-object-attrtypes-nil": { + attrTyp: types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "nil": nil, + }, + }, + }, + expected: true, + }, + "list-number": { + attrTyp: types.ListType{ + ElemType: types.NumberType, + }, + expected: false, + }, + "list-set-nil": { + attrTyp: types.ListType{ + ElemType: types.SetType{}, + }, + expected: true, + }, + "list-set-string": { + attrTyp: types.ListType{ + ElemType: types.SetType{ + ElemType: types.StringType, + }, + }, + expected: false, + }, + "list-string": { + attrTyp: types.ListType{ + ElemType: types.StringType, + }, + expected: false, + }, + "list-tuple-nil": { + attrTyp: types.ListType{ + ElemType: types.TupleType{}, + }, + expected: false, // expected as tuples can be empty + }, + "list-tuple-elemtypes": { + attrTyp: types.ListType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.StringType, + }, + }, + }, + expected: false, + }, + "list-tuple-elemtypes-nil": { + attrTyp: types.ListType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.StringType, + nil, + }, + }, + }, + expected: true, + }, + "map-nil": { + attrTyp: types.MapType{}, + expected: true, + }, + "map-bool": { + attrTyp: types.MapType{ + ElemType: types.BoolType, + }, + expected: false, + }, + "map-custom-bool": { + attrTyp: types.MapType{ + ElemType: testtypes.BoolType{}, + }, + expected: false, + }, + "map-custom-dynamic": { + attrTyp: types.MapType{ + ElemType: testtypes.DynamicType{}, + }, + expected: false, + }, + "map-custom-float64": { + attrTyp: types.MapType{ + ElemType: testtypes.Float64Type{}, + }, + expected: false, + }, + "map-custom-int64": { + attrTyp: types.MapType{ + ElemType: testtypes.Int64Type{}, + }, + expected: false, + }, + "map-custom-list-nil": { + attrTyp: types.MapType{ + ElemType: testtypes.ListType{}, + }, + // While testtypes.ListType embeds basetypes.ListType and this test + // case does not specify an ElemType value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // ElementType() method which would be used for custom types. + expected: false, + }, + "map-custom-list-string": { + attrTyp: types.MapType{ + ElemType: testtypes.ListType{ + ListType: types.ListType{ + ElemType: types.StringType, + }, + }, + }, + expected: false, + }, + "map-custom-map-nil": { + attrTyp: types.MapType{ + ElemType: testtypes.MapType{}, + }, + // While testtypes.MapType embeds basetypes.MapType and this test + // case does not specify an ElemType value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // ElementType() method which would be used for custom types. + expected: false, + }, + "map-custom-map-string": { + attrTyp: types.MapType{ + ElemType: testtypes.MapType{ + MapType: types.MapType{ + ElemType: types.StringType, + }, + }, + }, + expected: false, + }, + "map-custom-number": { + attrTyp: types.MapType{ + ElemType: testtypes.NumberType{}, + }, + expected: false, + }, + "map-custom-object-nil": { + attrTyp: types.MapType{ + ElemType: testtypes.ObjectType{}, + }, + expected: false, // expected as objects can be empty + }, + "map-custom-object-attrtypes": { + attrTyp: types.MapType{ + ElemType: testtypes.ObjectType{ + ObjectType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + }, + expected: false, + }, + "map-custom-object-attrtypes-nil": { + attrTyp: types.MapType{ + ElemType: testtypes.ObjectType{ + ObjectType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": nil, + }, + }, + }, + }, + // While testtypes.ObjectType embeds basetypes.ObjectType and this + // test case specifies a nil AttrTypes value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // AttributeTypes() method which would be used for custom types. + expected: false, + }, + "map-custom-set-nil": { + attrTyp: types.MapType{ + ElemType: testtypes.SetType{}, + }, + // While testtypes.SetType embeds basetypes.SetType and this test + // case does not specify an ElemType value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // ElementType() method which would be used for custom types. + expected: false, + }, + "map-custom-set-string": { + attrTyp: types.MapType{ + ElemType: testtypes.SetType{ + SetType: types.SetType{ + ElemType: types.StringType, + }, + }, + }, + expected: false, + }, + "map-custom-string": { + attrTyp: types.MapType{ + ElemType: testtypes.StringType{}, + }, + expected: false, + }, + "map-dynamic": { + attrTyp: types.MapType{ + ElemType: types.DynamicType, + }, + expected: false, + }, + "map-float64": { + attrTyp: types.MapType{ + ElemType: types.Float64Type, + }, + expected: false, + }, + "map-int64": { + attrTyp: types.MapType{ + ElemType: types.Int64Type, + }, + expected: false, + }, + "map-list-nil": { + attrTyp: types.MapType{ + ElemType: types.ListType{}, + }, + expected: true, + }, + "map-list-string": { + attrTyp: types.MapType{ + ElemType: types.ListType{ + ElemType: types.StringType, + }, + }, + expected: false, + }, + "map-map-nil": { + attrTyp: types.MapType{ + ElemType: types.MapType{}, + }, + expected: true, + }, + "map-map-string": { + attrTyp: types.MapType{ + ElemType: types.MapType{ + ElemType: types.StringType, + }, + }, + expected: false, + }, + "map-object-nil": { + attrTyp: types.MapType{ + ElemType: types.ObjectType{}, + }, + expected: false, // expected as objects can be empty + }, + "map-object-attrtypes": { + attrTyp: types.MapType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + expected: false, + }, + "map-object-attrtypes-nil": { + attrTyp: types.MapType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "nil": nil, + }, + }, + }, + expected: true, + }, + "map-number": { + attrTyp: types.MapType{ + ElemType: types.NumberType, + }, + expected: false, + }, + "map-set-nil": { + attrTyp: types.MapType{ + ElemType: types.SetType{}, + }, + expected: true, + }, + "map-set-string": { + attrTyp: types.MapType{ + ElemType: types.SetType{ + ElemType: types.StringType, + }, + }, + expected: false, + }, + "map-string": { + attrTyp: types.MapType{ + ElemType: types.StringType, + }, + expected: false, + }, + "map-tuple-nil": { + attrTyp: types.MapType{ + ElemType: types.TupleType{}, + }, + expected: false, // expected as tuples can be empty + }, + "map-tuple-elemtypes": { + attrTyp: types.MapType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.StringType, + }, + }, + }, + expected: false, + }, + "map-tuple-elemtypes-nil": { + attrTyp: types.MapType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.StringType, + nil, + }, + }, + }, + expected: true, + }, + "number": { + attrTyp: types.NumberType, + expected: false, + }, + "object-nil": { + attrTyp: types.ObjectType{}, + expected: false, // expected as objects can be empty + }, + "object-custom-list-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": testtypes.ListType{}, + }, + }, + // While testtypes.ListType embeds basetypes.ListType and this test + // case does not specify an ElemType value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // ElementType() method which would be used for custom types. + expected: false, + }, + "object-custom-list-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": testtypes.ListType{ + ListType: types.ListType{ + ElemType: types.StringType, + }, + }, + }, + }, + expected: false, + }, + "object-custom-list-list-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": testtypes.ListType{ + ListType: types.ListType{ + ElemType: types.ListType{}, + }, + }, + }, + }, + // Similar to other custom type test cases, the custom type will + // prevent the further checking of the element type. + expected: false, + }, + "object-custom-list-list-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": testtypes.ListType{ + ListType: types.ListType{ + ElemType: types.ListType{ + ElemType: types.StringType, + }, + }, + }, + }, + }, + expected: false, + }, + "object-custom-list-map-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": testtypes.ListType{ + ListType: types.ListType{ + ElemType: types.MapType{}, + }, + }, + }, + }, + // Similar to other custom type test cases, the custom type will + // prevent the further checking of the element type. + expected: false, + }, + "object-custom-list-map-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": testtypes.ListType{ + ListType: types.ListType{ + ElemType: types.MapType{ + ElemType: types.StringType, + }, + }, + }, + }, + }, + expected: false, + }, + "object-custom-list-object-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": testtypes.ListType{ + ListType: types.ListType{ + ElemType: types.ObjectType{}, + }, + }, + }, + }, + expected: false, // expected as objects can be empty + }, + "object-custom-list-object-attrtypes": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": testtypes.ListType{ + ListType: types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + }, + }, + }, + expected: false, + }, + "object-custom-list-object-attrtypes-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": testtypes.ListType{ + ListType: types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "nil": nil, + }, + }, + }, + }, + }, + }, + // Similar to other custom type test cases, the custom type will + // prevent the further checking of the element type. + expected: false, + }, + "object-custom-list-set-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": testtypes.ListType{ + ListType: types.ListType{ + ElemType: types.SetType{}, + }, + }, + }, + }, + // Similar to other custom type test cases, the custom type will + // prevent the further checking of the element type. + expected: false, + }, + "object-custom-list-set-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": testtypes.ListType{ + ListType: types.ListType{ + ElemType: types.SetType{ + ElemType: types.StringType, + }, + }, + }, + }, + }, + expected: false, + }, + "object-custom-list-tuple-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": testtypes.ListType{ + ListType: types.ListType{ + ElemType: types.TupleType{}, + }, + }, + }, + }, + expected: false, // expected as tuples can be empty + }, + "object-custom-list-tuple-elemtypes": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": testtypes.ListType{ + ListType: types.ListType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + types.Float64Type, + }, + }, + }, + }, + }, + }, + expected: false, + }, + "object-custom-list-tuple-elemtypes-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": testtypes.ListType{ + ListType: types.ListType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + nil, + }, + }, + }, + }, + }, + }, + // Similar to other custom type test cases, the custom type will + // prevent the further checking of the element type. + expected: false, + }, + "object-custom-map-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": testtypes.MapType{}, + }, + }, + // While testtypes.MapType embeds basetypes.MapType and this test + // case does not specify an ElemType value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // ElementType() method which would be used for custom types. + expected: false, + }, + "object-custom-map-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": testtypes.MapType{ + MapType: types.MapType{ + ElemType: types.StringType, + }, + }, + }, + }, + expected: false, + }, + "object-custom-map-list-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": testtypes.MapType{ + MapType: types.MapType{ + ElemType: types.ListType{}, + }, + }, + }, + }, + // Similar to other custom type test cases, the custom type will + // prevent the further checking of the element type. + expected: false, + }, + "object-custom-map-list-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": testtypes.MapType{ + MapType: types.MapType{ + ElemType: types.ListType{ + ElemType: types.StringType, + }, + }, + }, + }, + }, + expected: false, + }, + "object-custom-map-map-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": testtypes.MapType{ + MapType: types.MapType{ + ElemType: types.MapType{}, + }, + }, + }, + }, + // Similar to other custom type test cases, the custom type will + // prevent the further checking of the element type. + expected: false, + }, + "object-custom-map-map-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": testtypes.MapType{ + MapType: types.MapType{ + ElemType: types.MapType{ + ElemType: types.StringType, + }, + }, + }, + }, + }, + expected: false, + }, + "object-custom-map-object-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": testtypes.MapType{ + MapType: types.MapType{ + ElemType: types.ObjectType{}, + }, + }, + }, + }, + expected: false, // expected as objects can be empty + }, + "object-custom-map-object-attrtypes": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": testtypes.MapType{ + MapType: types.MapType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + }, + }, + }, + expected: false, + }, + "object-custom-map-object-attrtypes-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": testtypes.MapType{ + MapType: types.MapType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "nil": nil, + }, + }, + }, + }, + }, + }, + // Similar to other custom type test cases, the custom type will + // prevent the further checking of the element type. + expected: false, + }, + "object-custom-map-set-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": testtypes.MapType{ + MapType: types.MapType{ + ElemType: types.SetType{}, + }, + }, + }, + }, + // Similar to other custom type test cases, the custom type will + // prevent the further checking of the element type. + expected: false, + }, + "object-custom-map-set-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": testtypes.MapType{ + MapType: types.MapType{ + ElemType: types.SetType{ + ElemType: types.StringType, + }, + }, + }, + }, + }, + expected: false, + }, + "object-custom-map-tuple-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": testtypes.MapType{ + MapType: types.MapType{ + ElemType: types.TupleType{}, + }, + }, + }, + }, + expected: false, // expected as tuples can be empty + }, + "object-custom-map-tuple-elemtypes": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": testtypes.MapType{ + MapType: types.MapType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + types.Float64Type, + }, + }, + }, + }, + }, + }, + expected: false, + }, + "object-custom-map-tuple-elemtypes-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": testtypes.MapType{ + MapType: types.MapType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + nil, + }, + }, + }, + }, + }, + }, + // Similar to other custom type test cases, the custom type will + // prevent the further checking of the element type. + expected: false, + }, + "object-custom-object-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "object": testtypes.ObjectType{}, + }, + }, + expected: false, // expected as objects can be empty + }, + "object-custom-object-attrtypes": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "object": testtypes.ObjectType{ + ObjectType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + }, + }, + expected: false, + }, + "object-custom-object-attrtypes-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "object": testtypes.ObjectType{ + ObjectType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "nil": nil, + }, + }, + }, + }, + }, + // Similar to other custom type test cases, the custom type will + // prevent the further checking of the element type. + expected: false, + }, + "object-custom-set-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": testtypes.SetType{}, + }, + }, + // While testtypes.SetType embeds basetypes.SetType and this test + // case does not specify an ElemType value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // ElementType() method which would be used for custom types. + expected: false, + }, + "object-custom-set-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": testtypes.SetType{ + SetType: types.SetType{ + ElemType: types.StringType, + }, + }, + }, + }, + expected: false, + }, + "object-custom-set-list-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": testtypes.SetType{ + SetType: types.SetType{ + ElemType: types.ListType{}, + }, + }, + }, + }, + expected: false, + }, + "object-custom-set-list-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": testtypes.SetType{ + SetType: types.SetType{ + ElemType: types.ListType{ + ElemType: types.StringType, + }, + }, + }, + }, + }, + expected: false, + }, + "object-custom-set-map-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": testtypes.SetType{ + SetType: types.SetType{ + ElemType: types.MapType{}, + }, + }, + }, + }, + // Similar to other custom type test cases, the custom type will + // prevent the further checking of the element type. + expected: false, + }, + "object-custom-set-map-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": testtypes.SetType{ + SetType: types.SetType{ + ElemType: types.MapType{ + ElemType: types.StringType, + }, + }, + }, + }, + }, + expected: false, + }, + "object-custom-set-object-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": testtypes.SetType{ + SetType: types.SetType{ + ElemType: types.ObjectType{}, + }, + }, + }, + }, + expected: false, // expected as objects can be empty + }, + "object-custom-set-object-attrtypes": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": testtypes.SetType{ + SetType: types.SetType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + }, + }, + }, + expected: false, + }, + "object-custom-set-object-attrtypes-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": testtypes.SetType{ + SetType: types.SetType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "nil": nil, + }, + }, + }, + }, + }, + }, + // Similar to other custom type test cases, the custom type will + // prevent the further checking of the element type. + expected: false, + }, + "object-custom-set-set-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": testtypes.SetType{ + SetType: types.SetType{ + ElemType: types.SetType{}, + }, + }, + }, + }, + expected: false, + }, + "object-custom-set-set-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": testtypes.SetType{ + SetType: types.SetType{ + ElemType: types.SetType{ + ElemType: types.StringType, + }, + }, + }, + }, + }, + expected: false, + }, + "object-custom-set-tuple-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": testtypes.SetType{ + SetType: types.SetType{ + ElemType: types.TupleType{}, + }, + }, + }, + }, + expected: false, // expected as tuples can be empty + }, + "object-custom-set-tuple-elemtypes": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": testtypes.SetType{ + SetType: types.SetType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + types.Float64Type, + }, + }, + }, + }, + }, + }, + expected: false, + }, + "object-custom-set-tuple-elemtypes-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": testtypes.SetType{ + SetType: types.SetType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + nil, + }, + }, + }, + }, + }, + }, + // Similar to other custom type test cases, the custom type will + // prevent the further checking of the element type. + expected: false, + }, + "object-list-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": types.ListType{}, + }, + }, + expected: true, + }, + "object-list-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": types.ListType{ + ElemType: types.StringType, + }, + }, + }, + expected: false, + }, + "object-list-list-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": types.ListType{ + ElemType: types.ListType{}, + }, + }, + }, + expected: true, + }, + "object-list-list-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": types.ListType{ + ElemType: types.ListType{ + ElemType: types.StringType, + }, + }, + }, + }, + expected: false, + }, + "object-list-map-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": types.ListType{ + ElemType: types.MapType{}, + }, + }, + }, + expected: true, + }, + "object-list-map-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": types.ListType{ + ElemType: types.MapType{ + ElemType: types.StringType, + }, + }, + }, + }, + expected: false, + }, + "object-list-object-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": types.ListType{ + ElemType: types.ObjectType{}, + }, + }, + }, + expected: false, // expected as objects can be empty + }, + "object-list-object-attrtypes": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + }, + }, + expected: false, + }, + "object-list-object-attrtypes-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "nil": nil, + }, + }, + }, + }, + }, + expected: true, + }, + "object-list-set-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": types.ListType{ + ElemType: types.SetType{}, + }, + }, + }, + expected: true, + }, + "object-list-set-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": types.ListType{ + ElemType: types.SetType{ + ElemType: types.StringType, + }, + }, + }, + }, + expected: false, + }, + "object-list-tuple-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": types.ListType{ + ElemType: types.TupleType{}, + }, + }, + }, + expected: false, // expected as tuples can be empty + }, + "object-list-tuple-elemtypes": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": types.ListType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + types.Float64Type, + }, + }, + }, + }, + }, + expected: false, + }, + "object-list-tuple-elemtypes-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list": types.ListType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + nil, + }, + }, + }, + }, + }, + expected: true, + }, + "object-map-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": types.MapType{}, + }, + }, + expected: true, + }, + "object-map-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": types.MapType{ + ElemType: types.StringType, + }, + }, + }, + expected: false, + }, + "object-map-list-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": types.MapType{ + ElemType: types.ListType{}, + }, + }, + }, + expected: true, + }, + "object-map-list-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": types.MapType{ + ElemType: types.ListType{ + ElemType: types.StringType, + }, + }, + }, + }, + expected: false, + }, + "object-map-map-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": types.MapType{ + ElemType: types.MapType{}, + }, + }, + }, + expected: true, + }, + "object-map-map-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": types.MapType{ + ElemType: types.MapType{ + ElemType: types.StringType, + }, + }, + }, + }, + expected: false, + }, + "object-map-object-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": types.MapType{ + ElemType: types.ObjectType{}, + }, + }, + }, + expected: false, // expected as objects can be empty + }, + "object-map-object-attrtypes": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": types.MapType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + }, + }, + expected: false, + }, + "object-map-object-attrtypes-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": types.MapType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "nil": nil, + }, + }, + }, + }, + }, + expected: true, + }, + "object-map-set-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": types.MapType{ + ElemType: types.SetType{}, + }, + }, + }, + expected: true, + }, + "object-map-set-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": types.MapType{ + ElemType: types.SetType{ + ElemType: types.StringType, + }, + }, + }, + }, + expected: false, + }, + "object-map-tuple-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": types.MapType{ + ElemType: types.TupleType{}, + }, + }, + }, + expected: false, // expected as tuples can be empty + }, + "object-map-tuple-elemtypes": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": types.MapType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + types.Float64Type, + }, + }, + }, + }, + }, + expected: false, + }, + "object-map-tuple-elemtypes-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "map": types.MapType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + nil, + }, + }, + }, + }, + }, + expected: true, + }, + "object-object-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "object": types.ObjectType{}, + }, + }, + expected: false, // expected as objects can be empty + }, + "object-object-attrtypes": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "object": types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + }, + expected: false, + }, + "object-object-attrtypes-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "object": types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "nil": nil, + }, + }, + }, + }, + expected: true, + }, + "object-set-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": types.SetType{}, + }, + }, + expected: true, + }, + "object-set-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": types.SetType{ + ElemType: types.StringType, + }, + }, + }, + expected: false, + }, + "object-set-list-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": types.SetType{ + ElemType: types.ListType{}, + }, + }, + }, + expected: true, + }, + "object-set-list-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": types.SetType{ + ElemType: types.ListType{ + ElemType: types.StringType, + }, + }, + }, + }, + expected: false, + }, + "object-set-map-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": types.SetType{ + ElemType: types.MapType{}, + }, + }, + }, + expected: true, + }, + "object-set-map-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": types.SetType{ + ElemType: types.MapType{ + ElemType: types.StringType, + }, + }, + }, + }, + expected: false, + }, + "object-set-object-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": types.SetType{ + ElemType: types.ObjectType{}, + }, + }, + }, + expected: false, // expected as objects can be empty + }, + "object-set-object-attrtypes": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": types.SetType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + }, + }, + expected: false, + }, + "object-set-object-attrtypes-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": types.SetType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "nil": nil, + }, + }, + }, + }, + }, + expected: true, + }, + "object-set-set-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": types.SetType{ + ElemType: types.SetType{}, + }, + }, + }, + expected: true, + }, + "object-set-set-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": types.SetType{ + ElemType: types.SetType{ + ElemType: types.StringType, + }, + }, + }, + }, + expected: false, + }, + "object-set-tuple-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": types.SetType{ + ElemType: types.TupleType{}, + }, + }, + }, + expected: false, // expected as tuples can be empty + }, + "object-set-tuple-elemtypes": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": types.SetType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + types.Float64Type, + }, + }, + }, + }, + }, + expected: false, + }, + "object-set-tuple-elemtypes-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "set": types.SetType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + nil, + }, + }, + }, + }, + }, + expected: true, + }, + "object-tuple-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "tuple": types.TupleType{}, + }, + }, + expected: false, // expected as tuples can be empty + }, + "object-tuple-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "tuple": types.TupleType{ + ElemTypes: []attr.Type{types.StringType}, + }, + }, + }, + expected: false, + }, + "object-tuple-list-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "tuple": types.TupleType{ + ElemTypes: []attr.Type{types.ListType{}}, + }, + }, + }, + expected: true, + }, + "object-tuple-list-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "tuple": types.TupleType{ + ElemTypes: []attr.Type{ + types.ListType{ + ElemType: types.StringType, + }, + }, + }, + }, + }, + expected: false, + }, + "object-tuple-map-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "tuple": types.TupleType{ + ElemTypes: []attr.Type{types.MapType{}}, + }, + }, + }, + expected: true, + }, + "object-tuple-map-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "tuple": types.TupleType{ + ElemTypes: []attr.Type{ + types.MapType{ + ElemType: types.StringType, + }, + }, + }, + }, + }, + expected: false, + }, + "object-tuple-object-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "tuple": types.TupleType{ + ElemTypes: []attr.Type{types.ObjectType{}}, + }, + }, + }, + expected: false, // expected as objects can be empty + }, + "object-tuple-object-attrtypes": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "tuple": types.TupleType{ + ElemTypes: []attr.Type{ + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + }, + }, + }, + expected: false, + }, + "object-tuple-object-attrtypes-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "tuple": types.TupleType{ + ElemTypes: []attr.Type{ + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "nil": nil, + }, + }, + }, + }, + }, + }, + expected: true, + }, + "object-tuple-set-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "tuple": types.TupleType{ + ElemTypes: []attr.Type{types.SetType{}}, + }, + }, + }, + expected: true, + }, + "object-tuple-set-string": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "tuple": types.TupleType{ + ElemTypes: []attr.Type{ + types.SetType{ + ElemType: types.StringType, + }, + }, + }, + }, + }, + expected: false, + }, + "object-tuple-tuple-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "tuple": types.TupleType{ + ElemTypes: []attr.Type{types.TupleType{}}, + }, + }, + }, + expected: false, // expected as tuples can be empty + }, + "object-tuple-tuple-elemtypes": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "tuple": types.TupleType{ + ElemTypes: []attr.Type{ + types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + types.Float64Type, + }, + }, + }, + }, + }, + }, + expected: false, + }, + "object-tuple-tuple-elemtypes-nil": { + attrTyp: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "tuple": types.TupleType{ + ElemTypes: []attr.Type{ + types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + nil, + }, + }, + }, + }, + }, + }, + expected: true, + }, + "set-nil": { + attrTyp: types.SetType{}, + expected: true, + }, + "set-bool": { + attrTyp: types.SetType{ + ElemType: types.BoolType, + }, + expected: false, + }, + "set-custom-bool": { + attrTyp: types.SetType{ + ElemType: testtypes.BoolType{}, + }, + expected: false, + }, + "set-custom-dynamic": { + attrTyp: types.SetType{ + ElemType: testtypes.DynamicType{}, + }, + expected: false, + }, + "set-custom-float64": { + attrTyp: types.SetType{ + ElemType: testtypes.Float64Type{}, + }, + expected: false, + }, + "set-custom-int64": { + attrTyp: types.SetType{ + ElemType: testtypes.Int64Type{}, + }, + expected: false, + }, + "set-custom-list-nil": { + attrTyp: types.SetType{ + ElemType: testtypes.ListType{}, + }, + // While testtypes.ListType embeds basetypes.ListType and this test + // case does not specify an ElemType value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // ElementType() method which would be used for custom types. + expected: false, + }, + "set-custom-list-string": { + attrTyp: types.SetType{ + ElemType: testtypes.ListType{ + ListType: types.ListType{ + ElemType: types.StringType, + }, + }, + }, + expected: false, + }, + "set-custom-map-nil": { + attrTyp: types.SetType{ + ElemType: testtypes.MapType{}, + }, + // While testtypes.MapType embeds basetypes.MapType and this test + // case does not specify an ElemType value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // ElementType() method which would be used for custom types. + expected: false, + }, + "set-custom-map-string": { + attrTyp: types.SetType{ + ElemType: testtypes.MapType{ + MapType: types.MapType{ + ElemType: types.StringType, + }, + }, + }, + expected: false, + }, + "set-custom-number": { + attrTyp: types.SetType{ + ElemType: testtypes.NumberType{}, + }, + expected: false, + }, + "set-custom-object-nil": { + attrTyp: types.SetType{ + ElemType: testtypes.ObjectType{}, + }, + expected: false, // expected as objects can be empty + }, + "set-custom-object-attrtypes": { + attrTyp: types.SetType{ + ElemType: testtypes.ObjectType{ + ObjectType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + }, + expected: false, + }, + "set-custom-object-attrtypes-nil": { + attrTyp: types.SetType{ + ElemType: testtypes.ObjectType{ + ObjectType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": nil, + }, + }, + }, + }, + // While testtypes.ObjectType embeds basetypes.ObjectType and this + // test case specifies a nil AttrTypes value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // AttributeTypes() method which would be used for custom types. + expected: false, + }, + "set-custom-set-nil": { + attrTyp: types.SetType{ + ElemType: testtypes.SetType{}, + }, + // While testtypes.SetType embeds basetypes.SetType and this test + // case does not specify an ElemType value, the function logic is + // coded to only handle basetypes implementations due to the + // unexported missingType that would be returned from the + // ElementType() method which would be used for custom types. + expected: false, + }, + "set-custom-set-string": { + attrTyp: types.SetType{ + ElemType: testtypes.SetType{ + SetType: types.SetType{ + ElemType: types.StringType, + }, + }, + }, + expected: false, + }, + "set-custom-string": { + attrTyp: types.SetType{ + ElemType: testtypes.StringType{}, + }, + expected: false, + }, + "set-dynamic": { + attrTyp: types.SetType{ + ElemType: types.DynamicType, + }, + expected: false, + }, + "set-float64": { + attrTyp: types.SetType{ + ElemType: types.Float64Type, + }, + expected: false, + }, + "set-int64": { + attrTyp: types.SetType{ + ElemType: types.Int64Type, + }, + expected: false, + }, + "set-list-nil": { + attrTyp: types.SetType{ + ElemType: types.ListType{}, + }, + expected: true, + }, + "set-list-string": { + attrTyp: types.SetType{ + ElemType: types.ListType{ + ElemType: types.StringType, + }, + }, + expected: false, + }, + "set-map-nil": { + attrTyp: types.SetType{ + ElemType: types.MapType{}, + }, + expected: true, + }, + "set-map-string": { + attrTyp: types.SetType{ + ElemType: types.MapType{ + ElemType: types.StringType, + }, + }, + expected: false, + }, + "set-object-nil": { + attrTyp: types.SetType{ + ElemType: types.ObjectType{}, + }, + expected: false, // expected as objects can be empty + }, + "set-object-attrtypes": { + attrTyp: types.SetType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + expected: false, + }, + "set-object-attrtypes-nil": { + attrTyp: types.SetType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "nil": nil, + }, + }, + }, + expected: true, + }, + "set-number": { + attrTyp: types.SetType{ + ElemType: types.NumberType, + }, + expected: false, + }, + "set-set-nil": { + attrTyp: types.SetType{ + ElemType: types.SetType{}, + }, + expected: true, + }, + "set-set-string": { + attrTyp: types.SetType{ + ElemType: types.SetType{ + ElemType: types.StringType, + }, + }, + expected: false, + }, + "set-string": { + attrTyp: types.SetType{ + ElemType: types.StringType, + }, + expected: false, + }, + "set-tuple-nil": { + attrTyp: types.SetType{ + ElemType: types.TupleType{}, + }, + expected: false, // expected as tuples can be empty + }, + "set-tuple-elemtypes": { + attrTyp: types.SetType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.StringType, + }, + }, + }, + expected: false, + }, + "set-tuple-elemtypes-nil": { + attrTyp: types.SetType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.StringType, + nil, + }, + }, + }, + expected: true, + }, + "string": { + attrTyp: types.StringType, + expected: false, + }, + "tuple-nil": { + attrTyp: types.TupleType{}, + expected: false, // expected as tuples can be empty + }, + "tuple-bool": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{types.BoolType}, + }, + expected: false, + }, + "tuple-dynamic": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{types.DynamicType}, + }, + expected: false, + }, + "tuple-float64": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{types.Float64Type}, + }, + expected: false, + }, + "tuple-int64": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{types.Int64Type}, + }, + expected: false, + }, + "tuple-list-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.ListType{}, + }, + }, + expected: true, + }, + "tuple-list-string": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.ListType{ + ElemType: types.StringType, + }, + }, + }, + expected: false, + }, + "tuple-list-list-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.ListType{ + ElemType: types.ListType{}, + }, + }, + }, + expected: true, + }, + "tuple-list-list-string": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.ListType{ + ElemType: types.ListType{ + ElemType: types.StringType, + }, + }, + }, + }, + expected: false, + }, + "tuple-list-object-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.ListType{ + ElemType: types.ObjectType{}, + }, + }, + }, + expected: false, // expected as objects can be empty + }, + "tuple-list-object-attrtypes": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + }, + }, + expected: false, + }, + "tuple-list-object-attrtypes-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "nil": nil, + }, + }, + }, + }, + }, + expected: true, + }, + "tuple-list-tuple-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.ListType{ + ElemType: types.TupleType{}, + }, + }, + }, + expected: false, // expected as tuples can be empty + }, + "tuple-list-tuple-elemtypes": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.ListType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + types.Float64Type, + }, + }, + }, + }, + }, + expected: false, + }, + "tuple-list-tuple-elemtypes-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.ListType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + nil, + }, + }, + }, + }, + }, + expected: true, + }, + "tuple-map-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.MapType{}, + }, + }, + expected: true, + }, + "tuple-map-string": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.MapType{ + ElemType: types.StringType, + }, + }, + }, + expected: false, + }, + "tuple-map-map-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.MapType{ + ElemType: types.MapType{}, + }, + }, + }, + expected: true, + }, + "tuple-map-map-string": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.MapType{ + ElemType: types.MapType{ + ElemType: types.StringType, + }, + }, + }, + }, + expected: false, + }, + "tuple-map-object-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.MapType{ + ElemType: types.ObjectType{}, + }, + }, + }, + expected: false, // expected as objects can be empty + }, + "tuple-map-object-attrtypes": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.MapType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + }, + }, + expected: false, + }, + "tuple-map-object-attrtypes-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.MapType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "nil": nil, + }, + }, + }, + }, + }, + expected: true, + }, + "tuple-map-tuple-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.MapType{ + ElemType: types.TupleType{}, + }, + }, + }, + expected: false, // expected as tuples can be empty + }, + "tuple-map-tuple-elemtypes": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.MapType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + types.Float64Type, + }, + }, + }, + }, + }, + expected: false, + }, + "tuple-map-tuple-elemtypes-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.MapType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + nil, + }, + }, + }, + }, + }, + expected: true, + }, + "tuple-number": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{types.NumberType}, + }, + expected: false, + }, + "tuple-object-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.ObjectType{}, + }, + }, + expected: false, // expected as objects can be empty + }, + "tuple-object-attrtypes": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + }, + expected: false, + }, + "tuple-object-attrtypes-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "nil": nil, + }, + }, + }, + }, + expected: true, + }, + "tuple-set-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.SetType{}, + }, + }, + expected: true, + }, + "tuple-set-object-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.SetType{ + ElemType: types.ObjectType{}, + }, + }, + }, + expected: false, // expected as objects can be empty + }, + "tuple-set-object-attrtypes": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.SetType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "float64": types.Float64Type, + }, + }, + }, + }, + }, + expected: false, + }, + "tuple-set-object-attrtypes-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.SetType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "nil": nil, + }, + }, + }, + }, + }, + expected: true, + }, + "tuple-set-set-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.SetType{ + ElemType: types.SetType{}, + }, + }, + }, + expected: true, + }, + "tuple-set-set-string": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.SetType{ + ElemType: types.SetType{ + ElemType: types.StringType, + }, + }, + }, + }, + expected: false, + }, + "tuple-set-string": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.SetType{ + ElemType: types.StringType, + }, + }, + }, + expected: false, + }, + "tuple-set-tuple-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.SetType{ + ElemType: types.TupleType{}, + }, + }, + }, + expected: false, // expected as tuples can be empty + }, + "tuple-set-tuple-elemtypes": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.SetType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + types.Float64Type, + }, + }, + }, + }, + }, + expected: false, + }, + "tuple-set-tuple-elemtypes-nil": { + attrTyp: types.TupleType{ + ElemTypes: []attr.Type{ + types.SetType{ + ElemType: types.TupleType{ + ElemTypes: []attr.Type{ + types.BoolType, + nil, + }, + }, + }, + }, + }, + expected: true, + }, + } + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := fwtype.ContainsMissingUnderlyingType(testCase.attrTyp) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +}