# Tests that evaluating a value through a builtin (regexp.FindNamedSubmatch)
# and then back into a value does not cause incorrect cycle detection.
-- in.cue --
import "regexp"

// Issue #655
// When evaluating a value into a struct, and then back into a value, the
// evaluation mode flips from Partial to AllConjunctsDone to Back. This is
// typically not an issue, but if a referred field is within a struct generated
// by a builtin, effectively the entire struct needs to be evaluated and special
// care should be taking to not evaluate too early.
builtinCyclePerm: {
	X: "mod.test"

	Y: {
		#components: regexp.FindNamedSubmatch(#"^(?P<host>[[:alnum:].]+)$"#, X)
		host:        #components.host
	}

	X: Y.host
}
-- matchn.cue --
// This should not be a structural cycle, as the list type is "optional".
issue3410: {
	_s
	_s: {
		#x: matchN(1, [_s, [..._s]])
	}
}
issue3420: {
	matches1: {
		#S: matchN(1, [_, _|_])
		s: 2
	}
}
issue3443: {
	matchIf: {
		#S: matchIf({x?: "b"}, {n?: #S & (int | {})}, _)
	}
	noCycle: {
		// This is not a structural cycle, and should not hang, as n? is optional.
		#S: matchN(1, [{n?: #S & (int | {})}])

		noHang: {
			s: #S
			s: n: n: _
		}
	}

	noCycle2: {
		#S: matchN(1, [{n?: (int | #S)}])
	}

	cycle1: {
		// This correct CUE, as matchN allows for schema to be errors. We should
		// probably have a vet rule to catch this,though.
		#S: matchN(1, [{n: #S}])

		// This is not an error as the result is structure shared. Not sure if
		// this should be accepted.
		ok: {
			s: #S
			s: _
		}

		// This unifies deep enough to cause a structural cycle. It probably
		// should not.
		cycle: {
			s: #S
			s: n: n: _
		}
	}

	cycle2: {
		// TODO: this should probably fail, or at least be consistent with
		// cycle1.cycle.
		fail: #S: matchN(1, [{n: #S}]) & {n: n: n: _}
	}
}
selfRefConcrete: {
	// A matchN whose schema list refers back to the hidden field being
	// validated makes no progress when that field is also unified with a
	// concrete value. This must be reported as a structural cycle rather
	// than causing the evaluator to loop forever.
	_x1: matchN(2, [_x1, _x1])
	x: 3 & _x1

	// matchIf is subject to the same kind of self-referential cycle.
	_y1: matchIf(_y1, _y1, _y1)
	y:   3 & _y1
}
issue3633: final: {
	// With nested matchNs as below, the schema will become an incomplete error
	// as a value of the root vertex. Make sure this edge case is properly
	// handled.
	data: {} & #s
	#s: matchN(1, [matchN(1, [{a!: _}])])
}
issue3842: reduced: {
	#recursive: [...matchIf({
		source?: "multi"
	}, #recursive, _) & {
		source?: string
	}]
}
-- cycle.cue --
import "strconv"

noCycle: t1: {
	_s
	_s: {
		#x: matchN(1, [_s])
		#x: {} // insert concrete value to trigger application of validator.
	}
}
issue3649: noCycle: t1: {
	data: #c
	data: a: b: "foo"
	#c: {
		// There should be no cycle because of these optional fields.
		b?: string
		a?: matchN(1, [#c])
	}
}
// Same as above, but with longer paths.
issue3649: noCycle: t2: {
	x: c
	x: y: a: d: b: "foo"
	c: {
		b?: string
		y: a?: matchN(1, [{d: c}])
	}
}
issue3649: cycle: t1: {
	data: #c
	data: a: b: "foo"
	#c: {
		b: string
		a: matchN(1, [#c])
	}
}

// This used to cause a stack overflow.
issue4037: cycle: {
	_intlike: x=matchN(1, [strconv.Atoi(x)])
	f:        "1233" & _intlike
}
-- yamlcycle.cue --
import "encoding/yaml"

yamlNoCycle: {
	// the validator is invoked recursively and also unifies with a concrete
	// value. This should nonetheless not result in a cycle error.
	#c: {
		b?: string
		a?: yaml.Validate(#c)
	}
	data: #c
	data: a: #"{a: "b: foo"}"#
}
selfCycle: t1: {
	c: matchN(1, [{d: c}])
	c: d: {}
}
selfCycle: t2: {
	// Even though only part of the path is unified with concrete data, this
	// should still result in a cycle error.
	c: matchN(1, [{d: c}])
	c: {}
}
selfCycle: yamlVal: t1: {
	x: y: yaml.Validate(x)
	x: y: "{}"
}
selfCycle: yamlVal: t2: {
	// Duplicating validators can cause some tricky code paths, like finalizing
	// a node while still in the middle of validation.
	x: y: yaml.Validate(x)
	x: y: "{}"
	x: y: yaml.Validate(x)
	x: y: "{}"
}
selfCycle: yamlFun: t1: {
	x: y?: yaml.Validate("{}", x)
}
selfCycle: yamlFun: t2: {
	z: x & __no_sharing
	z: y: "{}"
	x: y: yaml.Validate("{}", x)
}
selfCycle: yamlValidatePartial: {
	x: y: yaml.ValidatePartial(x)
	x: y: "{}"
}
-- jsoncycle.cue --
import "encoding/json"

jsonCycle: t1: {
	x: y: json.Validate(x)
	x: y: "{}"
}
-- listmatchncycle.cue --
import "list"

listMatchN: structCycle: {
	x: y: list.MatchN(1, x)
	x: y: [{}]
}
listMatchN: ok: {
	x: y?: list.MatchN(1, x)
	z: x
	z: y: [{}]
}
-- issue3634.cue --
// evalv3 structural cycle regression: using `len(...)` with a `#Schema`
// embedding inside `list.Repeat` caused a spurious structural cycle error
// that did not occur in evalv2.
// https://cuelang.org/issue/3634
import "list"

issue3634: full: {
	#Schema: {
		required?: [...string]
		properties?: [string]: null | #Schema
	}

	out: len([...#Schema] & list.Repeat([{
		#Schema // removing this fixes the bug!
		properties: foo: required: ["bar", "baz"]
	}], 3))
}
issue3634: reduced: {
	#D: {
		b?: int
		a?: #D
	}
	out: len(#D & list.Repeat([#D & {a: b: 1}], 1)[0])
}
-- issue3743.cue --
// Passing a cyclic value to `yaml.MarshalStream` caused a stack overflow
// instead of a clean structural cycle error.
// https://cuelang.org/issue/3743
import "encoding/yaml"

issue3743: {
	#transform: {
		in: _
		out: [
			if false {out}, // note that this is a cycle
			in,
		]
	}

	docs: [(#transform & {in: "x"}).out]
	out: yaml.MarshalStream(docs)
}
-- out/compile --
--- cycle.cue
{
  noCycle: {
    t1: {
      〈0;_s〉
      _s: {
        #x: matchN(1, [
          〈2;_s〉,
        ])
        #x: {}
      }
    }
  }
  issue3649: {
    noCycle: {
      t1: {
        data: 〈0;#c〉
        data: {
          a: {
            b: "foo"
          }
        }
        #c: {
          b?: string
          a?: matchN(1, [
            〈2;#c〉,
          ])
        }
      }
    }
  }
  issue3649: {
    noCycle: {
      t2: {
        x: 〈0;c〉
        x: {
          y: {
            a: {
              d: {
                b: "foo"
              }
            }
          }
        }
        c: {
          b?: string
          y: {
            a?: matchN(1, [
              {
                d: 〈4;c〉
              },
            ])
          }
        }
      }
    }
  }
  issue3649: {
    cycle: {
      t1: {
        data: 〈0;#c〉
        data: {
          a: {
            b: "foo"
          }
        }
        #c: {
          b: string
          a: matchN(1, [
            〈2;#c〉,
          ])
        }
      }
    }
  }
  issue4037: {
    cycle: {
      _intlike: matchN(1, [
        〈import;strconv〉.Atoi(〈1〉),
      ])
      f: ("1233" & 〈0;_intlike〉)
    }
  }
}
--- in.cue
{
  builtinCyclePerm: {
    X: "mod.test"
    Y: {
      #components: 〈import;regexp〉.FindNamedSubmatch("^(?P<host>[[:alnum:].]+)$", 〈1;X〉)
      host: 〈0;#components〉.host
    }
    X: 〈0;Y〉.host
  }
}
--- issue3634.cue
{
  issue3634: {
    full: {
      #Schema: {
        required?: [
          ...string,
        ]
        properties?: {
          [string]: (null|〈2;#Schema〉)
        }
      }
      out: len(([
        ...〈1;#Schema〉,
      ] & 〈import;list〉.Repeat([
        {
          〈2;#Schema〉
          properties: {
            foo: {
              required: [
                "bar",
                "baz",
              ]
            }
          }
        },
      ], 3)))
    }
  }
  issue3634: {
    reduced: {
      #D: {
        b?: int
        a?: 〈1;#D〉
      }
      out: len((〈0;#D〉 & 〈import;list〉.Repeat([
        (〈1;#D〉 & {
          a: {
            b: 1
          }
        }),
      ], 1)[0]))
    }
  }
}
--- issue3743.cue
{
  issue3743: {
    #transform: {
      in: _
      out: [
        if false {
          〈2;out〉
        },
        〈1;in〉,
      ]
    }
    docs: [
      (〈1;#transform〉 & {
        in: "x"
      }).out,
    ]
    out: 〈import;"encoding/yaml"〉.MarshalStream(〈0;docs〉)
  }
}
--- jsoncycle.cue
{
  jsonCycle: {
    t1: {
      x: {
        y: 〈import;"encoding/json"〉.Validate(〈1;x〉)
      }
      x: {
        y: "{}"
      }
    }
  }
}
--- listmatchncycle.cue
{
  listMatchN: {
    structCycle: {
      x: {
        y: 〈import;list〉.MatchN(1, 〈1;x〉)
      }
      x: {
        y: [
          {},
        ]
      }
    }
  }
  listMatchN: {
    ok: {
      x: {
        y?: 〈import;list〉.MatchN(1, 〈1;x〉)
      }
      z: 〈0;x〉
      z: {
        y: [
          {},
        ]
      }
    }
  }
}
--- matchn.cue
{
  issue3410: {
    〈0;_s〉
    _s: {
      #x: matchN(1, [
        〈2;_s〉,
        [
          ...〈3;_s〉,
        ],
      ])
    }
  }
  issue3420: {
    matches1: {
      #S: matchN(1, [
        _,
        _|_(explicit error (_|_ literal) in source),
      ])
      s: 2
    }
  }
  issue3443: {
    matchIf: {
      #S: 〈1;matchIf〉({
        x?: "b"
      }, {
        n?: (〈1;#S〉 & (int|{}))
      }, _)
    }
    noCycle: {
      #S: matchN(1, [
        {
          n?: (〈2;#S〉 & (int|{}))
        },
      ])
      noHang: {
        s: 〈1;#S〉
        s: {
          n: {
            n: _
          }
        }
      }
    }
    noCycle2: {
      #S: matchN(1, [
        {
          n?: (int|〈2;#S〉)
        },
      ])
    }
    cycle1: {
      #S: matchN(1, [
        {
          n: 〈2;#S〉
        },
      ])
      ok: {
        s: 〈1;#S〉
        s: _
      }
      cycle: {
        s: 〈1;#S〉
        s: {
          n: {
            n: _
          }
        }
      }
    }
    cycle2: {
      fail: {
        #S: (matchN(1, [
          {
            n: 〈2;#S〉
          },
        ]) & {
          n: {
            n: {
              n: _
            }
          }
        })
      }
    }
  }
  selfRefConcrete: {
    _x1: matchN(2, [
      〈1;_x1〉,
      〈1;_x1〉,
    ])
    x: (3 & 〈0;_x1〉)
    _y1: matchIf(〈0;_y1〉, 〈0;_y1〉, 〈0;_y1〉)
    y: (3 & 〈0;_y1〉)
  }
  issue3633: {
    final: {
      data: ({} & 〈0;#s〉)
      #s: matchN(1, [
        matchN(1, [
          {
            a!: _
          },
        ]),
      ])
    }
  }
  issue3842: {
    reduced: {
      #recursive: [
        ...(matchIf({
          source?: "multi"
        }, 〈1;#recursive〉, _) & {
          source?: string
        }),
      ]
    }
  }
}
--- yamlcycle.cue
{
  yamlNoCycle: {
    #c: {
      b?: string
      a?: 〈import;"encoding/yaml"〉.Validate(〈1;#c〉)
    }
    data: 〈0;#c〉
    data: {
      a: "{a: \"b: foo\"}"
    }
  }
  selfCycle: {
    t1: {
      c: matchN(1, [
        {
          d: 〈2;c〉
        },
      ])
      c: {
        d: {}
      }
    }
  }
  selfCycle: {
    t2: {
      c: matchN(1, [
        {
          d: 〈2;c〉
        },
      ])
      c: {}
    }
  }
  selfCycle: {
    yamlVal: {
      t1: {
        x: {
          y: 〈import;"encoding/yaml"〉.Validate(〈1;x〉)
        }
        x: {
          y: "{}"
        }
      }
    }
  }
  selfCycle: {
    yamlVal: {
      t2: {
        x: {
          y: 〈import;"encoding/yaml"〉.Validate(〈1;x〉)
        }
        x: {
          y: "{}"
        }
        x: {
          y: 〈import;"encoding/yaml"〉.Validate(〈1;x〉)
        }
        x: {
          y: "{}"
        }
      }
    }
  }
  selfCycle: {
    yamlFun: {
      t1: {
        x: {
          y?: 〈import;"encoding/yaml"〉.Validate("{}", 〈1;x〉)
        }
      }
    }
  }
  selfCycle: {
    yamlFun: {
      t2: {
        z: (〈0;x〉 & _|_(no sharing))
        z: {
          y: "{}"
        }
        x: {
          y: 〈import;"encoding/yaml"〉.Validate("{}", 〈1;x〉)
        }
      }
    }
  }
  selfCycle: {
    yamlValidatePartial: {
      x: {
        y: 〈import;"encoding/yaml"〉.ValidatePartial(〈1;x〉)
      }
      x: {
        y: "{}"
      }
    }
  }
}
