Can Semantic Equality Check in Custom Types be Asymmetrical?

Hi Terraform Framework team,

I am trying to migrate a DiffSuppressFunc from SDKv2 to the Framework.

var resourceSchema = map[string]*schema.Schema{
	"some_attr": {
		Type:         schema.TypeInt,
		DiffSuppressFunc: func(k, oldValue, newValue string, d *schema.ResourceData) bool {
			return newValue == ""
		// ... other fields

This DiffSuppressFunc does a asymmetrical equality check and won’t update the attribute only when the new value is an empty string. And if the newValue and oldValue are swapped, the result will be different.

I saw the recommended way is to migrate DiffSuppressFunc to the Custom Type with Semantic Equality checking in the Framework. But I wonder if the asymmetrical equality checking is guaranteed in the Semantic Equality checking. Like is it guaranteed that the old value will always be compared with the new value (oldValue.StringSemanticEquals(ctx, newValue)) and never in the opposite direction, as with newValue.StringSemanticEquals(ctx, oldValue) ?

And would it be possible to mention it in the document as a confirmation? Thank you!

Hi @zliang-akamai :wave:

The link you supplied uses the following semantic equality check:

// CustomStringValue defined in the value type section
// Ensure the implementation satisfies the expected interfaces
var _ basetypes.StringValuableWithSemanticEquals = CustomStringValue{}

func (v CustomStringValue) StringSemanticEquals(ctx context.Context, newValuable basetypes.StringValuable) (bool, diag.Diagnostics) {
    var diags diag.Diagnostics

    // The framework should always pass the correct value type, but always check
    newValue, ok := newValuable.(CustomStringValue)

    if !ok {
            "Semantic Equality Check Error",
            "An unexpected value type was received while performing semantic equality checks. "+
            "Please report this to the provider developers.\n\n"+
            "Expected Value Type: "+fmt.Sprintf("%T", v)+"\n"+
            "Got Value Type: "+fmt.Sprintf("%T", newValuable),

        return false, diags

    // Skipping error checking if CustomStringValue already implemented RFC3339 validation
    priorTime, _ := time.Parse(time.RFC3339, v.StringValue.ValueString())

    // Skipping error checking if CustomStringValue already implemented RFC3339 validation
    newTime, _ := time.Parse(time.RFC3339, newValue.ValueString())

    // If the times are equivalent, keep the prior value
    return priorTime.Equal(newTime), diags

The StringSemanticEquals method is defined on CustomStringValue which will always represent the oldValue. Because of the way in which semantic equality checks are implemented on custom types, semantic equality checking will always be oldValue.SemanticEquals(ctx, newValue).

1 Like

Thank you @bendbennett for the clarification!
Do you think the doc can be more explicit about that so others won’t be facing the same question when they are reading the doc?