// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package inline_test

import "testing"

// Testcases mostly come in pairs, of a success and a failure
// to substitute based on specific constant argument values.

func TestFalconStringIndex(t *testing.T) {
	runTests(t, []testcase{
		{
			"Non-negative string index.",
			`func f(i int) byte { return s[i] }; var s string`,
			`func _() { f(0) }`,
			`func _() { _ = s[0] }`,
		},
		{
			"Negative string index.",
			`func f(i int) byte { return s[i] }; var s string`,
			`func _() { f(-1) }`,
			`func _() {
	var i int = -1
	_ = s[i]
}`,
		},
		{
			"String index in range.",
			`func f(s string, i int) byte { return s[i] }`,
			`func _() { f("-", 0) }`,
			`func _() { _ = "-"[0] }`,
		},
		{
			"String index out of range.",
			`func f(s string, i int) byte { return s[i] }`,
			`func _() { f("-", 1) }`,
			`func _() {
	var (
		s string = "-"
		i int    = 1
	)
	_ = s[i]
}`,
		},
		{
			"Remove known prefix (OK)",
			`func f(s, prefix string) string { return s[:len(prefix)] }`,
			`func _() { f("", "") }`,
			`func _() { _ = ""[:len("")] }`,
		},
		{
			"Remove not-a-prefix (out of range)",
			`func f(s, prefix string) string { return s[:len(prefix)] }`,
			`func _() { f("", "pre") }`,
			`func _() {
	var s, prefix string = "", "pre"
	_ = s[:len(prefix)]
}`,
		},
	})
}

func TestFalconSliceIndices(t *testing.T) {
	runTests(t, []testcase{
		{
			"Monotonic (0<=i<=j) slice indices (len unknown).",
			`func f(i, j int) []int { return s[i:j] }; var s []int`,
			`func _() { f(0, 1) }`,
			`func _() { _ = s[0:1] }`,
		},
		{
			"Non-monotonic slice indices (len unknown).",
			`func f(i, j int) []int { return s[i:j] }; var s []int`,
			`func _() { f(1, 0) }`,
			`func _() {
	var i, j int = 1, 0
	_ = s[i:j]
}`,
		},
		{
			"Negative slice index.",
			`func f(i, j int) []int { return s[i:j] }; var s []int`,
			`func _() { f(-1, 1) }`,
			`func _() {
	var i, j int = -1, 1
	_ = s[i:j]
}`,
		},
	})
}

func TestFalconMapKeys(t *testing.T) {
	runTests(t, []testcase{
		{
			"Unique map keys (int)",
			`func f(x int) { _ = map[int]bool{1: true, x: true} }`,
			`func _() { f(2) }`,
			`func _() { _ = map[int]bool{1: true, 2: true} }`,
		},
		{
			"Duplicate map keys (int)",
			`func f(x int) { _ = map[int]bool{1: true, x: true} }`,
			`func _() { f(1) }`,
			`func _() {
	var x int = 1
	_ = map[int]bool{1: true, x: true}
}`,
		},
		{
			"Generic map keys",
			// We have to use a named map type here, because the substituter tries to
			// wrap the type expr in parens, which are not allowed around the type in
			// composite literal expressions.
			`type Map map[int]bool; func f[M Map](x int) { _ = M{1: true, x: true} }`,
			`func _() { f[Map](1) }`,
			`func _() {
	var x int = 1
	_ = Map{1: true, x: true}
}`,
		},
		{
			"Array keys", // not a map; shouldn't crash (golang/go#74393)
			`func f(x int) { _ = [2]int{1: 0} }`,
			`func _() { f(0) }`,
			`func _() { _ = [2]int{1: 0} }`,
		},
		{
			"Slice keys", // not a map; shouldn't crash (golang/go#74393)
			`func f(x int) { _ = []int{1: 0} }`,
			`func _() { f(0) }`,
			`func _() { _ = []int{1: 0} }`,
		},
		{
			"Unique map keys (varied built-in types)",
			`func f(x int16) { _ = map[any]bool{1: true, x: true} }`,
			`func _() { f(2) }`,
			`func _() { _ = map[any]bool{1: true, int16(2): true} }`,
		},
		{
			"Duplicate map keys (varied built-in types)",
			`func f(x int16) { _ = map[any]bool{1: true, x: true} }`,
			`func _() { f(1) }`,
			`func _() { _ = map[any]bool{1: true, int16(1): true} }`,
		},
		{
			"Unique map keys (varied user-defined types)",
			`func f(x myint) { _ = map[any]bool{1: true, x: true} }; type myint int`,
			`func _() { f(2) }`,
			`func _() { _ = map[any]bool{1: true, myint(2): true} }`,
		},
		{
			"Duplicate map keys (varied user-defined types)",
			`func f(x myint, y myint2) { _ = map[any]bool{x: true, y: true} }; type (myint int; myint2 int)`,
			`func _() { f(1, 1) }`,
			`func _() {
	var (
		x myint  = 1
		y myint2 = 1
	)
	_ = map[any]bool{x: true, y: true}
}`,
		},
		{
			"Duplicate map keys (user-defined alias to built-in)",
			`func f(x myint, y int) { _ = map[any]bool{x: true, y: true} }; type myint = int`,
			`func _() { f(1, 1) }`,
			`func _() {
	var (
		x myint = 1
		y int   = 1
	)
	_ = map[any]bool{x: true, y: true}
}`,
		},
	})
}

func TestFalconSwitchCases(t *testing.T) {
	runTests(t, []testcase{
		{
			"Unique switch cases (int).",
			`func f(x int) { switch 0 { case x: case 1: } }`,
			`func _() { f(2) }`,
			`func _() {
	switch 0 {
	case 2:
	case 1:
	}
}`,
		},
		{
			"Duplicate switch cases (int).",
			`func f(x int) { switch 0 { case x: case 1: } }`,
			`func _() { f(1) }`,
			`func _() {
	var x int = 1
	switch 0 {
	case x:
	case 1:
	}
}`,
		},
		{
			"Unique switch cases (varied built-in types).",
			`func f(x int) { switch any(nil) { case x: case int16(1): } }`,
			`func _() { f(2) }`,
			`func _() {
	switch any(nil) {
	case 2:
	case int16(1):
	}
}`,
		},
		{
			"Duplicate switch cases (varied built-in types).",
			`func f(x int) { switch any(nil) { case x: case int16(1): } }`,
			`func _() { f(1) }`,
			`func _() {
	switch any(nil) {
	case 1:
	case int16(1):
	}
}`,
		},
	})
}

func TestFalconDivision(t *testing.T) {
	runTests(t, []testcase{
		{
			"Division by two.",
			`func f(x, y int) int { return x / y }`,
			`func _() { f(1, 2) }`,
			`func _() { _ = 1 / 2 }`,
		},
		{
			"Division by zero.",
			`func f(x, y int) int { return x / y }`,
			`func _() { f(1, 0) }`,
			`func _() {
	var x, y int = 1, 0
	_ = x / y
}`,
		},
		{
			"Division by two (statement).",
			`func f(x, y int) { x /= y }`,
			`func _() { f(1, 2) }`,
			`func _() {
	var x int = 1
	x /= 2
}`,
		},
		{
			"Division by zero (statement).",
			`func f(x, y int) { x /= y }`,
			`func _() { f(1, 0) }`,
			`func _() {
	var x, y int = 1, 0
	x /= y
}`,
		},
		{
			"Division of minint by two (ok).",
			`func f(x, y int32) { _ = x / y }`,
			`func _() { f(-0x80000000, 2) }`,
			`func _() { _ = int32(-0x80000000) / int32(2) }`,
		},
		{
			"Division of minint by -1 (overflow).",
			`func f(x, y int32) { _ = x / y }`,
			`func _() { f(-0x80000000, -1) }`,
			`func _() {
	var x, y int32 = -0x80000000, -1
	_ = x / y
}`,
		},
	})
}

func TestFalconMinusMinInt(t *testing.T) {
	runTests(t, []testcase{
		{
			"Negation of maxint.",
			`func f(x int32) int32 { return -x }`,
			`func _() { f(0x7fffffff) }`,
			`func _() { _ = -int32(0x7fffffff) }`,
		},
		{
			"Negation of minint.",
			`func f(x int32) int32 { return -x }`,
			`func _() { f(-0x80000000) }`,
			`func _() {
	var x int32 = -0x80000000
	_ = -x
}`,
		},
	})
}

func TestFalconArithmeticOverflow(t *testing.T) {
	runTests(t, []testcase{
		{
			"Addition without overflow.",
			`func f(x, y int32) int32 { return x + y }`,
			`func _() { f(100, 200) }`,
			`func _() { _ = int32(100) + int32(200) }`,
		},
		{
			"Addition with overflow.",
			`func f(x, y int32) int32 { return x + y }`,
			`func _() { f(1<<30, 1<<30) }`,
			`func _() {
	var x, y int32 = 1 << 30, 1 << 30
	_ = x + y
}`,
		},
		{
			"Conversion in range.",
			`func f(x int) int8 { return int8(x) }`,
			`func _() { f(123) }`,
			`func _() { _ = int8(123) }`,
		},
		{
			"Conversion out of range.",
			`func f(x int) int8 { return int8(x) }`,
			`func _() { f(456) }`,
			`func _() {
	var x int = 456
	_ = int8(x)
}`,
		},
	})
}

func TestFalconComplex(t *testing.T) {
	runTests(t, []testcase{
		{
			"Complex arithmetic (good).",
			`func f(re, im float64, z complex128) byte { return "x"[int(real(complex(re, im)*complex(re, -im)-z))] }`,
			`func _() { f(1, 2, 5+0i) }`,
			// The float64 conversions are excessively conservative here
			// but in general may affect the type of complex produced.
			`func _() { _ = "x"[int(real(complex(float64(1), float64(2))*complex(float64(1), -2)-(5+0i)))] }`,
		},
		{
			"Complex arithmetic (bad).",
			`func f(re, im float64, z complex128) byte { return "x"[int(real(complex(re, im)*complex(re, -im)-z))] }`,
			`func _() { f(1, 3, 5+0i) }`,
			`func _() {
	var (
		re, im float64    = 1, 3
		z      complex128 = 5 + 0i
	)
	_ = "x"[int(real(complex(re, im)*complex(re, -im)-z))]
}`,
		},
	})
}
func TestFalconMisc(t *testing.T) {
	runTests(t, []testcase{
		{
			"Compound constant expression (good).",
			`func f(x, y string, i, j int) byte { return x[i*len(y)+j] }`,
			`func _() { f("abc", "xy", 2, -3) }`,
			`func _() { _ = "abc"[2*len("xy")+-3] }`,
		},
		{
			"Compound constant expression (index out of range).",
			`func f(x, y string, i, j int) byte { return x[i*len(y)+j] }`,
			`func _() { f("abc", "xy", 4, -3) }`,
			`func _() {
	var (
		x, y string = "abc", "xy"
		i, j int    = 4, -3
	)
	_ = x[i*len(y)+j]
}`,
		},
		{
			"Constraints within nested functions (good).",
			`func f(x int) { _ = func() { _ = [1]int{}[x] } }`,
			`func _() { f(0) }`,
			`func _() { _ = func() { _ = [1]int{}[0] } }`,
		},
		{
			"Constraints within nested functions (bad).",
			`func f(x int) { _ = func() { _ = [1]int{}[x] } }`,
			`func _() { f(1) }`,
			`func _() {
	var x int = 1
	_ = func() { _ = [1]int{}[x] }
}`,
		},
		{
			"Falcon violation rejects only the constant arguments (x, z).",
			`func f(x, y, z string) string { return x[:2] + y + z[:2] }; var b string`,
			`func _() { f("a", b, "c") }`,
			`func _() {
	var x, z string = "a", "c"
	_ = x[:2] + b + z[:2]
}`,
		},
	})
}
