# 4.5.3-1.3d 类型兼容性检查

类型的期望类型与实际类型是否兼容，是类型兼容性检查的核心，例如赋值语句`var name string = getName();`中，name 的期望类型是 string, 因此函数 getName() 的返回值必须也是 string, 类型检查器需要对此进行核实。

程序很多地方都会涉及到类型兼容性问题，例如赋值、函数定义、channel 写入等等，该问题的核心到最后往往体现为“能否将一个值赋给指定类型”，判断的核心逻辑定义在文件`$GCROOT/compile/internal/types2/assignments.go`中，入口函数是`check.assignment()`, 抛开细节，其主体逻辑如下:

```go
// x 是赋值语句的右值，而 T 是赋值语句左边的目标类型，context 用来记录上下文信息，例如 "return statement", 便于提示错误信息
func (check *Checker) assignment(x *operand, T Type, context string) {
	if isUntyped(x.typ) {
		// 处理 x 是 UntypedXXX 的情况，尝试将 x.val 的值转换给类型 T
		check.convertUntyped(x, target)
	}
	if reason := ""; !x.assignableTo(check, T, &reason) {
		// 判断 x 能够赋值给类型 T, 此处删除错误处理信息
	}
}
```

`assignableTo()`方法定义在文件`$GCROOT/compile/internal/types2/operand.go`中，删掉细节代码，其主体逻辑如下：

```go
func (x *operand) assignableTo(check *Checker, T Type, reason *string) bool {
	V := x.typ
	if check.identical(V, T) { // x 的类型与目标类型等价，则可以赋值
		return true
	}

	Vu := optype(V) // 拿到运算符的 underlying type
	Tu := optype(T) // 拿到目标类型的 underlying type

	// 处理 x 是常量值的情况
	if isUntyped(Vu) {
		switch t := Tu.(type) {
		case *Basic:
			// 目标类型是基础类型，此处判断 x 是否能够表示为该基础类型
		case *Sum:
			// 如果 x 可以赋值给 Sum 类型所带表的所有类型，则返回 true
			return t.is(func(t Type) bool {
				return x.assignableTo(check, t, reason)
			})
		case *Interface:
			check.completeInterface(nopos, t)
			return x.isNil() || t.Empty() // 如果 T 是 interface, 此时因为 x 是基础类型。那么只有当 x 是 nil, 或者 T 是 interface{} 时，x 才可以赋值给 T
		case *Pointer, *Signature, *Slice, *Map, *Chan:
			return x.isNil() // nil 可以赋值给指针、函数、切片、Map 以及 chan 类型
		}
	}

	/*
	 * 如果两个类型的 underlying type 是等价的，那么只要两个类型不同时是 defined type, 就可以进行赋值
	 * 详见 underlying type 一节的讨论
	 */
	if check.identical(Vu, Tu) && (!isNamed(V) || !isNamed(T)) {
		return true
	}

	// 如果 T 是 interface, 则判断 x 是否实现了该接口
	if Ti, ok := Tu.(*Interface); ok {
		// 判断类型 V 是否实现接口 Ti 的方式是检查 V 的方法集合是否覆盖了 Ti 的所有方法
		if m, wrongType := check.missingMethod(V, Ti, true); m != nil /* Implements(V, Ti) */ {
			// 找到缺失方法，进行错误处理
			return false
		}
		return true
	}

	// 判断两个 channel 类型是否可以赋值
	if Vc, ok := Vu.(*Chan); ok && Vc.dir == SendRecv {
		if Tc, ok := Tu.(*Chan); ok && check.identical(Vc.elem, Tc.elem) {
			return !isNamed(V) || !isNamed(T)
		}
	}

	return false
}
```

这两个方法是兼容性检查的主体思路，`check.identical()`我们已经在[类型的等价规则](/golang-bian-yi-qi-lei-xing-jian-cha/4.4.412-lei-xing-de-deng-jia-gui-ze-fl.md)中讨论过，UntypedXXX 的转换规则也已经在[基础类型的Untyped 部分](/golang-bian-yi-qi-lei-xing-jian-cha/4.4.43-lei-xing-shu-ju-jie-gou-ji-chu-lei-xing.md)中讨论过，后续章节会详细讨论 Underlying Type 的赋值规则，以及如何判断一个类型是否实现了某个接口。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://gocompiler.shizhz.me/golang-bian-yi-qi-lei-xing-jian-cha/4.5.31.3d-lei-xing-jian-rong-xing-jian-cha.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
