Skip to content

evalv3: Different result v0.12 vs 0.11 #3672

Open
@loisch

Description

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

cue version v0.12.0-dfe3ae033d26f79649d69c9dee6cc89ff7599c3d

go version go1.22.1
      -buildmode exe
       -compiler gc
        -ldflags -X cuelang.org/go/cmd/cue/cmd.version=v0.12.0-dfe3ae033d26f79649d69c9dee6cc89ff7599c3d
     CGO_ENABLED 1
          GOARCH arm64
            GOOS darwin
             vcs git
    vcs.revision dfe3ae033d26f79649d69c9dee6cc89ff7599c3d
        vcs.time 2025-01-10T08:17:07Z
    vcs.modified false
cue.lang.version v0.12.0

Does this issue reproduce with the latest stable release?

Yes, evalv3=1 produces a different result than evalv3=0 but evalv3 also produces a different result with the latest stable release than with the current master.

What did you do?

I've used a slightly different and simplified version of my program from issue #3654 to better understand cue semantics and performance. The testscript below passes for the latest build and shows a difference between the current and the new evaluator (the new evaluator giving the wrong result).

Correct result for cue eval out.cue -e value0 from current evaluator (current master and latest stable release):

Only: [{
    UseSectionName: true
}]
orgSyntax: "Only [UseSectionName]"

Wrong result from v0.12.0-dfe3ae033d26f79649d69c9dee6cc89ff7599c3d:

Only: [{
    UseSectionName: true
} | {
    UseSectionName: true
    Append: string | {
        UseSectionName: true
    }
} | {
    UseSectionName: true
    Prepend: string | {
        UseSectionName: true
    }
}]
orgSyntax: "Only [Append Impossible]"

Here it seems I'd have to prevent a unification between {UseSectionName: true} and {Append: #ConfigText} which probably has something do with closedness. It seems the new evaluator handles this differently. I don't even know where this unification happens.

The wrong result from new evaluator with cue version v0.11.1:

{
    Only: [{
        UseSectionName: true
    }]
    AllBut: []
    orgSyntax: "AllBut []"
} | {
    Only: [{
        UseSectionName: true
    }]
    orgSyntax: "Only [Append Impossible]"
}

The result is wrong because #ConfigText only has two alternatives string and { UseSectionName: true } and this the orgSyntax value from #orgForConfigText should never be the string Impossible because either of the two if checks should pass:

#ConfigText: string | {UseSectionName: true}

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

Before the latest fix for #3597 this triggered a panic but the root cause seems to be different because one of the ifs should match.

Reproducer

env CUE_EXPERIMENT=evalv3=1
exec cue eval out.cue -e value0.orgSyntax
stdout 'Only \[Append Impossible\]'
env CUE_EXPERIMENT=evalv3=0
exec cue eval out.cue -e value0.orgSyntax
stdout 'Only \[UseSectionName\]'

-- out.cue --
import "strings"

import "encoding/json"

#FinDefSetElemType: {
	#Type!: _
}
#FinDefSetDefinition: {
	#Elem!: #FinDefSetElemType
	#Alternatives: {
		allBut: {
			AllBut: [...#Elem.#Type]
		}
		only: {
			Only: [...#Elem.#Type]
		}
	}
}

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


#FinDefSetElemType: {
	#Type: _
	toString!: {
		_input:  #Type
		_output: string
	}
}

#FinDefSetDefinition: {
	#Elem!: #FinDefSetElemType
	#Alternatives: {
		allBut: {
			AllBut: [...#Elem.#Type]
			let xs = [for x in AllBut {(#Elem.toString & {_input: x})._output}]
			let str = strings.Join(xs, "\",\"")
			orgSyntax: "AllBut [\(str)]"
		}
		only: {
			Only: [...#Elem.#Type]
			let xs = [for x in Only {(#Elem.toString & {_input: x})._output}]
			let str = strings.Join(xs, "\",\"")
			orgSyntax: "Only [\(str)]"
		}
	}
}

#ConfigText: string | {UseSectionName: true}

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

#ConfigTextRules: #ConfigTextRule | [#ConfigTextRule, #ConfigTextRule, ...#ConfigTextRule]

#FinDefSetConfigTextElem: #FinDefSetElemType
#FinDefSetConfigTextElem: {
	#Type: #ConfigTextRules
}

#FinDefSetOfConfigText: (
	#mkFinDefSet & {
		#Definition: {#Elem: #FinDefSetConfigTextElem}
	}).#Type



















#FinDefSetConfigTextElem: {
	toString: {
		_input: #ConfigTextRules
		_output: (#orgForConfigTextRules & {source: _input}).orgSyntax
	}
}

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

#orgForConfigTextRule: {
	IN=source: #ConfigTextRule
	orgSyntax: {
		let INA = IN & {Append: _}
		let INP = IN & {Prepend: _}
		[
			if INA != _|_ {
				"Append " + (#orgForConfigText & {source: INA.Append}).orgSyntax
			},
			if INP != _|_ {
				"Prepend " + (#orgForConfigText & {source: INP.Prepend}).orgSyntax
			},
			(#orgForConfigText & {source: IN}).orgSyntax,
		][0]
	}
}

#orgForConfigTextRules: {
	IN=source: #ConfigTextRules
	orgSyntax: {
		let INL = IN & [...]
		[
			if INL != _|_ {
				let elems = [for x in IN {(#orgForConfigTextRule & {source: x}).orgSyntax}]
				let result = strings.Join(elems, "\",\"")
				"[\(result)]"
			},
			(#orgForConfigTextRule & {source: IN}).orgSyntax,
		][0]
	}
}

value0: #FinDefSetOfConfigText
value0: Only: [{ UseSectionName: true }]

What did you expect to see?

Same result with new evaluator and with old evaluator.

What did you see instead?

Different result for old and new evaluator.

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions