Skip to content

eval: unification rule doesn't hold (X & X != X) #3675

Open
@loisch

Description

I think the underlying cause for the bad performance in #3654 is a correctness issue with the evaluator. #3672 shows a problem with evalv3 but this issue shows an independent correctness problem with the current evaluator.

What version of CUE are you using (cue version)?

$ cue version
cue version v0.11.1

go version go1.23.4
      -buildmode exe
       -compiler gc
       -trimpath true
  DefaultGODEBUG asynctimerchan=1,gotypesalias=0,httpservecontentkeepheaders=1,tls3des=1,tlskyber=0,x509keypairleaf=0,x509negativeserial=1
     CGO_ENABLED 1
          GOARCH arm64
            GOOS darwin
         GOARM64 v8.0
cue.lang.version v0.11.1

Does this issue reproduce with the latest stable release?

Yes.

What did you do?

The following testscript shows that for term X = (*null | #FinDefSetOfConfigText) and some term Z the expression X & X & Z doesn't have the same value than just X & Z which violates rule 1 according to the definition for Unification in the CUE language specification (The unification of a with itself is always a.)

Both values (works2 and fails1) should be equal but they are not:

works2: (*null | #FinDefSetOfConfigText) & { Only: [{ UseSectionName: true }] } 
fails1: (*null | #FinDefSetOfConfigText) & (*null | #FinDefSetOfConfigText) & { Only: [{ UseSectionName: true }] }

I know that changing the program to

--- ../correctness.txtar	2025-01-13 17:02:22.106978377 +0100
+++ ../correctness2.txtar	2025-01-13 17:13:08.510166130 +0100
@@ -35,7 +35,7 @@
 		if (IN & string) != _|_ {
 			IN
 		},
-		if IN.UseSectionName != _|_ {
+		if (IN & {UseSectionName:_}) != _|_ {
 			"UseSectionName"
 		}
 	][0]

solves the issue. I don't know why because the surrounding definition #orgForConfigText is only used with newly constructed values (in the list comprehension) so the accidental unification of IN / source with {UseSectionName:_} shouldn't change anything.

But the important issue is that X & X & Z != X & Z.

exec cue export out.cue -e works1
exec cue export out.cue -e works2
exec cue export out.cue -e works3
! exec cue export out.cue -e fails1
! exec cue export out.cue -e fails2

-- out.cue --
import "strings"

#ConfigText: string | {UseSectionName: true}

#ConfigTextRule: #ConfigText | {Append: #ConfigText}

#FinDefSetDefinition: {
	#Alternatives: {
		only: {
			Only: [...#ConfigTextRule]
			let xs = [for x in Only {(#orgForConfigText & {source: x}).orgSyntax}]
			let str = strings.Join(xs, "\",\"")
			orgSyntax: "Only [\(str)]"
		}
	}
}

#mkFinDefSet: {
	#Definition!: #FinDefSetDefinition
	#Type: or([for _, v in #Definition.#Alternatives {v}])
}

#FinDefSetOfConfigText: #mkFinDefSet.#Type

#orgForConfigText: {
	IN=source: #ConfigText
	orgSyntax: [
		if (IN & string) != _|_ {
			IN
		},
		if IN.UseSectionName != _|_ {
			"UseSectionName"
		}
	][0]
}

works1: #FinDefSetOfConfigText & { Only: [{ UseSectionName: true }] } 
works2: (*null | #FinDefSetOfConfigText) & { Only: [{ UseSectionName: true }] } 
works3: (*null | #FinDefSetOfConfigText) & #FinDefSetOfConfigText & { Only: [{ UseSectionName: true }] } 
fails1: (*null | #FinDefSetOfConfigText) & (*null | #FinDefSetOfConfigText) & { Only: [{ UseSectionName: true }] }
fails2: #FinDefSetOfConfigText & (*null | #FinDefSetOfConfigText) & { Only: [{ UseSectionName: true }] }

What did you expect to see?

Failing testscript where all checks either fail or succeed.

What did you see instead?

Passing testscript showing that evaluation order matters and/or unifying a term with itself changes the meaning.

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions