// Copyright 2016 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.

// This file contains tests for sizes.

package types_test

import (
	"go/ast"
	"go/importer"
	"go/token"
	"go/types"
	"internal/testenv"
	"testing"
)

// findStructType typechecks src and returns the first struct type encountered.
func findStructType(t *testing.T, src string) *types.Struct {
	return findStructTypeConfig(t, src, &types.Config{})
}

func findStructTypeConfig(t *testing.T, src string, conf *types.Config) *types.Struct {
	types_ := make(map[ast.Expr]types.TypeAndValue)
	mustTypecheck("x", src, &types.Info{Types: types_})
	for _, tv := range types_ {
		if ts, ok := tv.Type.(*types.Struct); ok {
			return ts
		}
	}
	t.Fatalf("failed to find a struct type in src:\n%s\n", src)
	return nil
}

// Issue 16316
func TestMultipleSizeUse(t *testing.T) {
	const src = `
package main

type S struct {
    i int
    b bool
    s string
    n int
}
`
	ts := findStructType(t, src)
	sizes := types.StdSizes{WordSize: 4, MaxAlign: 4}
	if got := sizes.Sizeof(ts); got != 20 {
		t.Errorf("Sizeof(%v) with WordSize 4 = %d want 20", ts, got)
	}
	sizes = types.StdSizes{WordSize: 8, MaxAlign: 8}
	if got := sizes.Sizeof(ts); got != 40 {
		t.Errorf("Sizeof(%v) with WordSize 8 = %d want 40", ts, got)
	}
}

// Issue 16464
func TestAlignofNaclSlice(t *testing.T) {
	const src = `
package main

var s struct {
	x *int
	y []byte
}
`
	ts := findStructType(t, src)
	sizes := &types.StdSizes{WordSize: 4, MaxAlign: 8}
	var fields []*types.Var
	// Make a copy manually :(
	for i := 0; i < ts.NumFields(); i++ {
		fields = append(fields, ts.Field(i))
	}
	offsets := sizes.Offsetsof(fields)
	if offsets[0] != 0 || offsets[1] != 4 {
		t.Errorf("OffsetsOf(%v) = %v want %v", ts, offsets, []int{0, 4})
	}
}

func TestIssue16902(t *testing.T) {
	const src = `
package a

import "unsafe"

const _ = unsafe.Offsetof(struct{ x int64 }{}.x)
`
	fset := token.NewFileSet()
	f := mustParse(fset, "x.go", src)
	info := types.Info{Types: make(map[ast.Expr]types.TypeAndValue)}
	conf := types.Config{
		Importer: importer.Default(),
		Sizes:    &types.StdSizes{WordSize: 8, MaxAlign: 8},
	}
	_, err := conf.Check("x", fset, []*ast.File{f}, &info)
	if err != nil {
		t.Fatal(err)
	}
	for _, tv := range info.Types {
		_ = conf.Sizes.Sizeof(tv.Type)
		_ = conf.Sizes.Alignof(tv.Type)
	}
}

// Issue #53884.
func TestAtomicAlign(t *testing.T) {
	testenv.MustHaveGoBuild(t) // The Go command is needed for the importer to determine the locations of stdlib .a files.

	const src = `
package main

import "sync/atomic"

var s struct {
	x int32
	y atomic.Int64
	z int64
}
`

	want := []int64{0, 8, 16}
	for _, arch := range []string{"386", "amd64"} {
		t.Run(arch, func(t *testing.T) {
			conf := types.Config{
				Importer: importer.Default(),
				Sizes:    types.SizesFor("gc", arch),
			}
			ts := findStructTypeConfig(t, src, &conf)
			var fields []*types.Var
			// Make a copy manually :(
			for i := 0; i < ts.NumFields(); i++ {
				fields = append(fields, ts.Field(i))
			}

			offsets := conf.Sizes.Offsetsof(fields)
			if offsets[0] != want[0] || offsets[1] != want[1] || offsets[2] != want[2] {
				t.Errorf("OffsetsOf(%v) = %v want %v", ts, offsets, want)
			}
		})
	}
}
