# Tests that cyclic arithmetic constraints (#TimeSpan: start = end - duration
# etc.) within a task set unify and report errors correctly. The original
# issue involved a hang or very slow evaluation with interdependent tasks.
# https://cuelang.org/issue/3517
-- main.cue --
package p

#TimeSpan: {
	start:    float
	duration: float
	end:      float

	start:    end - duration
	duration: end - start
	end:      start + duration

	start: <=end
}

#Task: {
	action:   string
	timeSpan: #TimeSpan
}

#TaskSet: {
	action: string
	#tasks: [string]: #Task
	tasks: [for _, v in #tasks {v}]
	tasks: [...#Task]
	timeSpan?: #TimeSpan
}

#T1: #Task & {
	action: "Task 1"
	timeSpan: #TimeSpan & {
		duration: 4.2
	}
}

#T2: #Task & {
	action: "Task 2"
	timeSpan: #TimeSpan & {
		duration: 120.0
	}
}

ts: #TaskSet
ts: this={
	action: "Task 1 and Task 2"
	#tasks: {
		t1: #T1 & {
			timeSpan: end: this.#tasks.t2.timeSpan.start
		}
		t2: #T2 & {
			timeSpan: end: 119 // this.timeSpan.end
		}
	}
	timeSpan: start: this.#tasks.t1.timeSpan.start
}
@test(err, at=ts.#tasks.t1, code=eval, path=ts.#tasks.t2.timeSpan.end,
	contains="conflicting values %s and %s (mismatched types %s and %s)",
	args=[119, float, int, float],
	pos=[-49:12, -18:12, -6:7, -5:19],
)
@test(err, at=ts.#tasks.t2, code=eval, path=ts.#tasks.t2.timeSpan.end,
	contains="conflicting values %s and %s (mismatched types %s and %s)",
	args=[119, float, int, float],
	pos=[-54:12, -23:12, -11:7, -10:19],
)
-- out/todo.txt --
v2 showed ts.#tasks.t1 as successful (with concrete timeSpan values start=-5.2,
duration=4.2, end=-1.0), while v3 propagates the t2 error to t1 as well. v2
also reported different source positions for the error (./main.cue:17:12 and
./main.cue:35:6 instead of ./main.cue:37:12).
-- out/errors.txt --
[incomplete] #TimeSpan.start: non-concrete value end for bound <=:
    ./main.cue:12:11
#TimeSpan.start: non-concrete value float in operand to -:
    ./main.cue:8:12
    ./main.cue:6:12
    ./main.cue:10:12
#TimeSpan.duration: non-concrete value float in operand to -:
    ./main.cue:9:12
    ./main.cue:6:12
    ./main.cue:10:12
[incomplete] #TimeSpan.start: non-concrete value end for bound <=:
    ./main.cue:12:11
#TimeSpan.duration: non-concrete value float in operand to -:
    ./main.cue:9:12
    ./main.cue:6:12
    ./main.cue:10:12
[incomplete] #TimeSpan.start: non-concrete value end for bound <=:
    ./main.cue:12:11
#TimeSpan.duration: non-concrete value float in operand to -:
    ./main.cue:9:12
    ./main.cue:6:12
    ./main.cue:10:12
[incomplete] #T1.timeSpan.start: non-concrete value end for bound <=:
    ./main.cue:12:11
#T1.timeSpan.start: non-concrete value float in operand to -:
    ./main.cue:8:12
    ./main.cue:6:12
    ./main.cue:10:12
#T1.timeSpan.duration: non-concrete value float in operand to -:
    ./main.cue:9:12
    ./main.cue:6:12
    ./main.cue:10:12
[incomplete] #T1.timeSpan.start: non-concrete value end for bound <=:
    ./main.cue:12:11
#T1.timeSpan.start: non-concrete value float in operand to -:
    ./main.cue:8:12
    ./main.cue:6:12
    ./main.cue:10:12
[incomplete] #T1.timeSpan.start: non-concrete value end for bound <=:
    ./main.cue:12:11
#T1.timeSpan.start: non-concrete value float in operand to -:
    ./main.cue:8:12
    ./main.cue:6:12
    ./main.cue:10:12
[incomplete] #T2.timeSpan.start: non-concrete value end for bound <=:
    ./main.cue:12:11
#T2.timeSpan.start: non-concrete value float in operand to -:
    ./main.cue:8:12
    ./main.cue:6:12
    ./main.cue:10:12
#T2.timeSpan.duration: non-concrete value float in operand to -:
    ./main.cue:9:12
    ./main.cue:6:12
    ./main.cue:10:12
[incomplete] #T2.timeSpan.start: non-concrete value end for bound <=:
    ./main.cue:12:11
#T2.timeSpan.start: non-concrete value float in operand to -:
    ./main.cue:8:12
    ./main.cue:6:12
    ./main.cue:10:12
[incomplete] #T2.timeSpan.start: non-concrete value end for bound <=:
    ./main.cue:12:11
#T2.timeSpan.start: non-concrete value float in operand to -:
    ./main.cue:8:12
    ./main.cue:6:12
    ./main.cue:10:12
[eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
    ./main.cue:6:12
    ./main.cue:37:12
    ./main.cue:49:7
    ./main.cue:50:19
[eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
    ./main.cue:6:12
    ./main.cue:37:12
    ./main.cue:49:7
    ./main.cue:50:19
[eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
    ./main.cue:6:12
    ./main.cue:37:12
    ./main.cue:49:7
    ./main.cue:50:19
[eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
    ./main.cue:6:12
    ./main.cue:37:12
    ./main.cue:49:7
    ./main.cue:50:19
[eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
    ./main.cue:6:12
    ./main.cue:37:12
    ./main.cue:49:7
    ./main.cue:50:19
[eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
    ./main.cue:6:12
    ./main.cue:37:12
    ./main.cue:49:7
    ./main.cue:50:19
[eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
    ./main.cue:6:12
    ./main.cue:37:12
    ./main.cue:49:7
    ./main.cue:50:19
[eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
    ./main.cue:6:12
    ./main.cue:37:12
    ./main.cue:49:7
    ./main.cue:50:19
[eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
    ./main.cue:6:12
    ./main.cue:37:12
    ./main.cue:49:7
    ./main.cue:50:19
[eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
    ./main.cue:6:12
    ./main.cue:37:12
    ./main.cue:49:7
    ./main.cue:50:19
-- out/eval/stats --
Leaks:  0
Freed:  45
Reused: 22
Allocs: 23
Retain: 0

Unifications: 45
Conjuncts:    119
Disjuncts:    0

NumCloseIDs: 13
-- out/evalalpha --
Errors:
ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
    ./main.cue:6:12
    ./main.cue:37:12
    ./main.cue:49:7
    ./main.cue:50:19

Result:
(_|_){
  // [eval]
  #TimeSpan: (#struct){
    start: (_|_){
      // [incomplete] #TimeSpan.start: non-concrete value end for bound <=:
      //     ./main.cue:12:11
      // #TimeSpan.start: non-concrete value float in operand to -:
      //     ./main.cue:8:12
      //     ./main.cue:6:12
      //     ./main.cue:10:12
      // #TimeSpan.duration: non-concrete value float in operand to -:
      //     ./main.cue:9:12
      //     ./main.cue:6:12
      //     ./main.cue:10:12
    }
    duration: (_|_){
      // [incomplete] #TimeSpan.start: non-concrete value end for bound <=:
      //     ./main.cue:12:11
      // #TimeSpan.duration: non-concrete value float in operand to -:
      //     ./main.cue:9:12
      //     ./main.cue:6:12
      //     ./main.cue:10:12
    }
    end: (_|_){
      // [incomplete] #TimeSpan.start: non-concrete value end for bound <=:
      //     ./main.cue:12:11
      // #TimeSpan.duration: non-concrete value float in operand to -:
      //     ./main.cue:9:12
      //     ./main.cue:6:12
      //     ./main.cue:10:12
    }
  }
  #Task: (#struct){
    action: (string){ string }
    timeSpan: ~(#TimeSpan)
  }
  #TaskSet: (#struct){
    action: (string){ string }
    #tasks: (#struct){
    }
    tasks: (#list){
    }
    timeSpan?: ~(#TimeSpan)
  }
  #T1: (#struct){
    action: (string){ "Task 1" }
    timeSpan: (#struct){
      duration: (_|_){
        // [incomplete] #T1.timeSpan.start: non-concrete value end for bound <=:
        //     ./main.cue:12:11
        // #T1.timeSpan.start: non-concrete value float in operand to -:
        //     ./main.cue:8:12
        //     ./main.cue:6:12
        //     ./main.cue:10:12
        // #T1.timeSpan.duration: non-concrete value float in operand to -:
        //     ./main.cue:9:12
        //     ./main.cue:6:12
        //     ./main.cue:10:12
      }
      start: (_|_){
        // [incomplete] #T1.timeSpan.start: non-concrete value end for bound <=:
        //     ./main.cue:12:11
        // #T1.timeSpan.start: non-concrete value float in operand to -:
        //     ./main.cue:8:12
        //     ./main.cue:6:12
        //     ./main.cue:10:12
      }
      end: (_|_){
        // [incomplete] #T1.timeSpan.start: non-concrete value end for bound <=:
        //     ./main.cue:12:11
        // #T1.timeSpan.start: non-concrete value float in operand to -:
        //     ./main.cue:8:12
        //     ./main.cue:6:12
        //     ./main.cue:10:12
      }
    }
  }
  #T2: (#struct){
    action: (string){ "Task 2" }
    timeSpan: (#struct){
      duration: (_|_){
        // [incomplete] #T2.timeSpan.start: non-concrete value end for bound <=:
        //     ./main.cue:12:11
        // #T2.timeSpan.start: non-concrete value float in operand to -:
        //     ./main.cue:8:12
        //     ./main.cue:6:12
        //     ./main.cue:10:12
        // #T2.timeSpan.duration: non-concrete value float in operand to -:
        //     ./main.cue:9:12
        //     ./main.cue:6:12
        //     ./main.cue:10:12
      }
      start: (_|_){
        // [incomplete] #T2.timeSpan.start: non-concrete value end for bound <=:
        //     ./main.cue:12:11
        // #T2.timeSpan.start: non-concrete value float in operand to -:
        //     ./main.cue:8:12
        //     ./main.cue:6:12
        //     ./main.cue:10:12
      }
      end: (_|_){
        // [incomplete] #T2.timeSpan.start: non-concrete value end for bound <=:
        //     ./main.cue:12:11
        // #T2.timeSpan.start: non-concrete value float in operand to -:
        //     ./main.cue:8:12
        //     ./main.cue:6:12
        //     ./main.cue:10:12
      }
    }
  }
  ts: (_|_){
    // [eval]
    action: (string){ "Task 1 and Task 2" }
    #tasks: (_|_){
      // [eval]
      t1: (_|_){
        // [eval]
        timeSpan: (_|_){
          // [eval]
          end: (_|_){
            // [eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
            //     ./main.cue:6:12
            //     ./main.cue:37:12
            //     ./main.cue:49:7
            //     ./main.cue:50:19
          }
          duration: (_|_){
            // [eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
            //     ./main.cue:6:12
            //     ./main.cue:37:12
            //     ./main.cue:49:7
            //     ./main.cue:50:19
          }
          start: (_|_){
            // [eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
            //     ./main.cue:6:12
            //     ./main.cue:37:12
            //     ./main.cue:49:7
            //     ./main.cue:50:19
          }
        }
        action: (string){ "Task 1" }
      }
      t2: (_|_){
        // [eval]
        timeSpan: (_|_){
          // [eval]
          end: (_|_){
            // [eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
            //     ./main.cue:6:12
            //     ./main.cue:37:12
            //     ./main.cue:49:7
            //     ./main.cue:50:19
          }
          duration: (_|_){
            // [eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
            //     ./main.cue:6:12
            //     ./main.cue:37:12
            //     ./main.cue:49:7
            //     ./main.cue:50:19
          }
          start: (_|_){
            // [eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
            //     ./main.cue:6:12
            //     ./main.cue:37:12
            //     ./main.cue:49:7
            //     ./main.cue:50:19
          }
        }
        action: (string){ "Task 2" }
      }
    }
    timeSpan: (_|_){
      // [eval]
      start: (_|_){
        // [eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
        //     ./main.cue:6:12
        //     ./main.cue:37:12
        //     ./main.cue:49:7
        //     ./main.cue:50:19
      }
      duration: (_|_){
        // [eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
        //     ./main.cue:6:12
        //     ./main.cue:37:12
        //     ./main.cue:49:7
        //     ./main.cue:50:19
      }
      end: (_|_){
        // [eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
        //     ./main.cue:6:12
        //     ./main.cue:37:12
        //     ./main.cue:49:7
        //     ./main.cue:50:19
      }
    }
    tasks: (_|_){
      // [eval] ts.#tasks.t2.timeSpan.end: conflicting values 119 and float (mismatched types int and float):
      //     ./main.cue:6:12
      //     ./main.cue:37:12
      //     ./main.cue:49:7
      //     ./main.cue:50:19
    }
  }
}
