// Copyright 2013 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 implements printing of types.

package types

import (
	
	
	
	
	
	
	
)

// A Qualifier controls how named package-level objects are printed in
// calls to [TypeString], [ObjectString], and [SelectionString].
//
// These three formatting routines call the Qualifier for each
// package-level object O, and if the Qualifier returns a non-empty
// string p, the object is printed in the form p.O.
// If it returns an empty string, only the object name O is printed.
//
// Using a nil Qualifier is equivalent to using (*[Package]).Path: the
// object is qualified by the import path, e.g., "encoding/json.Marshal".
type Qualifier func(*Package) string

// RelativeTo returns a [Qualifier] that fully qualifies members of
// all packages other than pkg.
func ( *Package) Qualifier {
	if  == nil {
		return nil
	}
	return func( *Package) string {
		if  ==  {
			return "" // same package; unqualified
		}
		return .Path()
	}
}

// TypeString returns the string representation of typ.
// The [Qualifier] controls the printing of
// package-level objects, and may be nil.
func ( Type,  Qualifier) string {
	var  bytes.Buffer
	WriteType(&, , )
	return .String()
}

// WriteType writes the string representation of typ to buf.
// The [Qualifier] controls the printing of
// package-level objects, and may be nil.
func ( *bytes.Buffer,  Type,  Qualifier) {
	newTypeWriter(, ).typ()
}

// WriteSignature writes the representation of the signature sig to buf,
// without a leading "func" keyword. The [Qualifier] controls the printing
// of package-level objects, and may be nil.
func ( *bytes.Buffer,  *Signature,  Qualifier) {
	newTypeWriter(, ).signature()
}

type typeWriter struct {
	buf          *bytes.Buffer
	seen         map[Type]bool
	qf           Qualifier
	ctxt         *Context       // if non-nil, we are type hashing
	tparams      *TypeParamList // local type parameters
	paramNames   bool           // if set, write function parameter names, otherwise, write types only
	tpSubscripts bool           // if set, write type parameter indices as subscripts
	pkgInfo      bool           // package-annotate first unexported-type field to avoid confusing type description
}

func newTypeWriter( *bytes.Buffer,  Qualifier) *typeWriter {
	return &typeWriter{, make(map[Type]bool), , nil, nil, true, false, false}
}

func newTypeHasher( *bytes.Buffer,  *Context) *typeWriter {
	assert( != nil)
	return &typeWriter{, make(map[Type]bool), nil, , nil, false, false, false}
}

func ( *typeWriter) ( byte) {
	if .ctxt != nil {
		if  == ' ' {
			 = '#'
		}
		.buf.WriteByte()
		return
	}
	.buf.WriteByte()
	if  == ',' ||  == ';' {
		.buf.WriteByte(' ')
	}
}

func ( *typeWriter) ( string) {
	.buf.WriteString()
}

func ( *typeWriter) ( string) {
	if .ctxt != nil {
		panic()
	}
	.buf.WriteString("<" +  + ">")
}

func ( *typeWriter) ( Type) {
	if .seen[] {
		.error("cycle to " + goTypeName())
		return
	}
	.seen[] = true
	defer delete(.seen, )

	switch t := .(type) {
	case nil:
		.error("nil")

	case *Basic:
		// exported basic types go into package unsafe
		// (currently this is just unsafe.Pointer)
		if token.IsExported(.name) {
			if ,  := Unsafe.scope.Lookup(.name).(*TypeName);  != nil {
				.typeName()
				break
			}
		}
		.string(.name)

	case *Array:
		.byte('[')
		.string(strconv.FormatInt(.len, 10))
		.byte(']')
		.(.elem)

	case *Slice:
		.string("[]")
		.(.elem)

	case *Struct:
		.string("struct{")
		for ,  := range .fields {
			if  > 0 {
				.byte(';')
			}

			// If disambiguating one struct for another, look for the first unexported field.
			// Do this first in case of nested structs; tag the first-outermost field.
			 := false
			if .qf == nil && .pkgInfo && !token.IsExported(.name) {
				// note for embedded types, type name is field name, and "string" etc are lower case hence unexported.
				 = true
				.pkgInfo = false // only tag once
			}

			// This doesn't do the right thing for embedded type
			// aliases where we should print the alias name, not
			// the aliased type (see go.dev/issue/44410).
			if !.embedded {
				.string(.name)
				.byte(' ')
			}
			.(.typ)
			if  {
				.string(" /* package ")
				.string(.pkg.Path())
				.string(" */ ")
			}
			if  := .Tag();  != "" {
				.byte(' ')
				// TODO(rfindley) If tag contains blanks, replacing them with '#'
				//                in Context.TypeHash may produce another tag
				//                accidentally.
				.string(strconv.Quote())
			}
		}
		.byte('}')

	case *Pointer:
		.byte('*')
		.(.base)

	case *Tuple:
		.tuple(, false)

	case *Signature:
		.string("func")
		.signature()

	case *Union:
		// Unions only appear as (syntactic) embedded elements
		// in interfaces and syntactically cannot be empty.
		if .Len() == 0 {
			.error("empty union")
			break
		}
		for ,  := range .terms {
			if  > 0 {
				.string(termSep)
			}
			if .tilde {
				.byte('~')
			}
			.(.typ)
		}

	case *Interface:
		if .ctxt == nil {
			if  == universeAny.Type() {
				// When not hashing, we can try to improve type strings by writing "any"
				// for a type that is pointer-identical to universeAny. This logic should
				// be deprecated by more robust handling for aliases.
				.string("any")
				break
			}
			if  == asNamed(universeComparable.Type()).underlying {
				.string("interface{comparable}")
				break
			}
		}
		if .implicit {
			if len(.methods) == 0 && len(.embeddeds) == 1 {
				.(.embeddeds[0])
				break
			}
			// Something's wrong with the implicit interface.
			// Print it as such and continue.
			.string("/* implicit */ ")
		}
		.string("interface{")
		 := true
		if .ctxt != nil {
			.typeSet(.typeSet())
		} else {
			for ,  := range .methods {
				if ! {
					.byte(';')
				}
				 = false
				.string(.name)
				.signature(.typ.(*Signature))
			}
			for ,  := range .embeddeds {
				if ! {
					.byte(';')
				}
				 = false
				.()
			}
		}
		.byte('}')

	case *Map:
		.string("map[")
		.(.key)
		.byte(']')
		.(.elem)

	case *Chan:
		var  string
		var  bool
		switch .dir {
		case SendRecv:
			 = "chan "
			// chan (<-chan T) requires parentheses
			if ,  := .elem.(*Chan);  != nil && .dir == RecvOnly {
				 = true
			}
		case SendOnly:
			 = "chan<- "
		case RecvOnly:
			 = "<-chan "
		default:
			.error("unknown channel direction")
		}
		.string()
		if  {
			.byte('(')
		}
		.(.elem)
		if  {
			.byte(')')
		}

	case *Named:
		// If hashing, write a unique prefix for t to represent its identity, since
		// named type identity is pointer identity.
		if .ctxt != nil {
			.string(strconv.Itoa(.ctxt.getID()))
		}
		.typeName(.obj) // when hashing written for readability of the hash only
		if .inst != nil {
			// instantiated type
			.typeList(.inst.targs.list())
		} else if .ctxt == nil && .TypeParams().Len() != 0 { // For type hashing, don't need to format the TypeParams
			// parameterized type
			.tParamList(.TypeParams().list())
		}

	case *TypeParam:
		if .obj == nil {
			.error("unnamed type parameter")
			break
		}
		if  := tparamIndex(.tparams.list(), );  >= 0 {
			// The names of type parameters that are declared by the type being
			// hashed are not part of the type identity. Replace them with a
			// placeholder indicating their index.
			.string(fmt.Sprintf("$%d", ))
		} else {
			.string(.obj.name)
			if .tpSubscripts || .ctxt != nil {
				.string(subscript(.id))
			}
			// If the type parameter name is the same as a predeclared object
			// (say int), point out where it is declared to avoid confusing
			// error messages. This doesn't need to be super-elegant; we just
			// need a clear indication that this is not a predeclared name.
			// Note: types2 prints position information here - we can't do
			//       that because we don't have a token.FileSet accessible.
			if .ctxt == nil && Universe.Lookup(.obj.name) != nil {
				.string("/* type parameter */")
			}
		}

	case *Alias:
		.typeName(.obj)
		if .ctxt != nil {
			// TODO(gri) do we need to print the alias type name, too?
			.(Unalias(.obj.typ))
		}

	default:
		// For externally defined implementations of Type.
		// Note: In this case cycles won't be caught.
		.string(.String())
	}
}

// typeSet writes a canonical hash for an interface type set.
func ( *typeWriter) ( *_TypeSet) {
	assert(.ctxt != nil)
	 := true
	for ,  := range .methods {
		if ! {
			.byte(';')
		}
		 = false
		.string(.name)
		.signature(.typ.(*Signature))
	}
	switch {
	case .terms.isAll():
		// nothing to do
	case .terms.isEmpty():
		.string(.terms.String())
	default:
		var  []string
		for ,  := range .terms {
			// terms are not canonically sorted, so we sort their hashes instead.
			var  bytes.Buffer
			if .tilde {
				.WriteByte('~')
			}
			newTypeHasher(&, .ctxt).typ(.typ)
			 = append(, .String())
		}
		sort.Strings()
		if ! {
			.byte(';')
		}
		.string(strings.Join(, "|"))
	}
}

func ( *typeWriter) ( []Type) {
	.byte('[')
	for ,  := range  {
		if  > 0 {
			.byte(',')
		}
		.typ()
	}
	.byte(']')
}

func ( *typeWriter) ( []*TypeParam) {
	.byte('[')
	var  Type
	for ,  := range  {
		// Determine the type parameter and its constraint.
		// list is expected to hold type parameter names,
		// but don't crash if that's not the case.
		if  == nil {
			.error("nil type parameter")
			continue
		}
		if  > 0 {
			if .bound !=  {
				// bound changed - write previous one before advancing
				.byte(' ')
				.typ()
			}
			.byte(',')
		}
		 = .bound
		.typ()
	}
	if  != nil {
		.byte(' ')
		.typ()
	}
	.byte(']')
}

func ( *typeWriter) ( *TypeName) {
	.string(packagePrefix(.pkg, .qf))
	.string(.name)
}

func ( *typeWriter) ( *Tuple,  bool) {
	.byte('(')
	if  != nil {
		for ,  := range .vars {
			if  > 0 {
				.byte(',')
			}
			// parameter names are ignored for type identity and thus type hashes
			if .ctxt == nil && .name != "" && .paramNames {
				.string(.name)
				.byte(' ')
			}
			 := .typ
			if  &&  == len(.vars)-1 {
				if ,  := .(*Slice);  {
					.string("...")
					 = .elem
				} else {
					// special case:
					// append(s, "foo"...) leads to signature func([]byte, string...)
					if ,  := under().(*Basic);  == nil || .kind != String {
						.error("expected string type")
						continue
					}
					.typ()
					.string("...")
					continue
				}
			}
			.typ()
		}
	}
	.byte(')')
}

func ( *typeWriter) ( *Signature) {
	if .TypeParams().Len() != 0 {
		if .ctxt != nil {
			assert(.tparams == nil)
			.tparams = .TypeParams()
			defer func() {
				.tparams = nil
			}()
		}
		.tParamList(.TypeParams().list())
	}

	.tuple(.params, .variadic)

	 := .results.Len()
	if  == 0 {
		// no result
		return
	}

	.byte(' ')
	if  == 1 && (.ctxt != nil || .results.vars[0].name == "") {
		// single unnamed result (if type hashing, name must be ignored)
		.typ(.results.vars[0].typ)
		return
	}

	// multiple or named result(s)
	.tuple(.results, false)
}

// subscript returns the decimal (utf8) representation of x using subscript digits.
func subscript( uint64) string {
	const  = len("₀") // all digits 0...9 have the same utf8 width
	var  [32 * ]byte
	 := len()
	for {
		 -= 
		utf8.EncodeRune([:], '₀'+rune(%10)) // '₀' == U+2080
		 /= 10
		if  == 0 {
			break
		}
	}
	return string([:])
}