Golang 编译器代码浅析
  • 0. Golang 编译器代码浅析
  • 1. golang 编译器 - 前言
    • 1.1 编译器简介
    • 1.2 Golang 编译器
    • 1.3 Go 语言版本
    • 1.4 项目设置
    • 1.5 约定
    • 1.6 写作目的
  • 2. golang 编译器 - 词法分析
    • 2.1 简介
    • 2.2 代码结构
    • 2.3 处理字符
    • 2.4 扫描Token
    • 2.5 总结
  • 3.a 语法分析理论知识
    • 3A.1 语法分析简介
    • 3A.2 文法
    • 3A.3 语法解析
    • 3A.3.1 自顶向下(Top-Down)
    • 3A.3.2 自顶向下 - 递归下降
    • 3A.3.3 自顶向下 - LL(1)文法
    • 3A.3.4 自底向上(Bottom-Up)
    • 3A.3.5 自底向上 - LR(0)项集及SLR预测表
    • 3A.3.6 自底向上 - LR(1)、LALR
    • 3A.4 语法分析工具
    • 3A.5 总结
  • 3B. golang 编译器 - 语法分析
    • 3B.1 简介
    • 3B.2 代码结构
    • 3B.3 数据结构
    • 3B.4 构造语法树
    • 3B.5 Unit Test及AST可视化
  • 4. Golang 编译器 - 类型检查
    • 4.1 简介
    • 4.2 代码结构
    • 4.3 符号解析
    • 4.4.1 数据结构 - 作用域
    • 4.4.2 数据结构 - Package
    • 4.4.3 数据结构 - Object 对象
    • 4.4.4-1 类型数据结构 - 简介
    • 4.4.4-2 类型接口
    • 4.4.4-3 基础类型
    • 4.4.4-4 内置复合类型
    • 4.4.4-5 Struct 类型
    • 4.4.4-6 Interface 类型
    • 4.4.4-7 Named 类型
    • 4.4.4-8 Tuple 类型
    • 4.4.4-9 Sum 类型
    • 4.4.4-10 Function & Method 类型
    • 4.4.4-11 泛型类型
    • 4.4.4-12 类型的等价规则
    • 4.4.4-13 类型的比较规则
    • 4.4.4-14 总结
    • 4.4.5 类型检查器
    • 4.4.6 总结
    • 4.5.1 类型检查逻辑 - 包加载器
    • 4.5.2 类型检查逻辑 - 初始化
    • 4.5.2-1 全局作用域
    • 4.5.2-2 类型检查器
    • 4.5.3 类型检查逻辑 - 流程分析
    • 4.5.3-1.1 总体流程
    • 4.5.3-1.2 类型检查准备工作
    • 4.5.3-1.3 类型检查核心逻辑
    • 4.5.3-1.3a 总体介绍
    • 4.5.3-1.3b 类型表达式的类型检查
    • 4.5.3-1.3c 求值表达式的类型检查
    • 4.5.3-1.3d 类型兼容性检查
    • 4.5.3-1.3e 处理delayed队列
    • 4.5.3-1.4 构建初始化顺序
    • 4.5.3-1.5 总结
    • 4.5.3-2 特定问题分析
    • 4.5.3-2a 对象循环依赖检查
    • 4.5.3-2b 方法与属性查找
    • 4.5.3-2c Underlying Type
    • 4.6 如何测试
    • 4.7 总结
  • 5. Golang 编译器 - IR Tree
    • 5.1 简介
    • 5.2 代码结构
    • 5.3 数据结构
    • 5.4 处理逻辑
    • 5.5 编译日志
    • 5.6 Unit Test
    • 5.7 总结
  • 6. golang 编译器 - 初始化任务
    • 6.1 简介
    • 6.2 代码结构
    • 6.3 总体逻辑
    • 6.4 赋值语句
    • 6.5 编译日志
    • 6.6 Unit Test
    • 6.7 总结
  • 7. golang 编译器 - 清除无效代码
    • 7.1 简介
    • 7.2 处理逻辑
    • 7.3 Unit Test
  • 8. golang 编译器 - Inline
    • 8.1 简介
    • 8.2 Inline的问题
    • 8.3 代码结构
    • 8.4 处理逻辑
    • 8.4.1 遍历调用链
    • 8.4.2 内联判断
    • 8.4.3 内联操作
    • 8.4.4 编译日志
    • 8.4.5 Unit Test
    • 8.4.6 总结
  • 9. golang 编译器 - 逃逸分析
    • 9.1 什么是逃逸分析
    • 9.2 Go 的逃逸分析
    • 9.3 算法思路
    • 9.4 代码结构
    • 9.5 处理逻辑
    • 9.5.1总体逻辑
    • 9.5.2 数据结构
    • 9.5.3 构建数据流有向图
    • 9.5.4 逃逸分析
    • 9.6 编译日志
    • 9.7 Unit Test
    • 9.8 总结
  • 10. golang 编译器 - 函数编译及导出
    • 10.1 简介
    • 10.2 编译函数
    • 10.2.1 SSA
    • 10.2.2 ABI
    • 10.2.3 并发控制
    • 10.3 导出对象文件
    • 10.4 总结
  • 11. Golang 编译器 - 写在最后
由 GitBook 提供支持
在本页

这有帮助吗?

  1. 4. Golang 编译器 - 类型检查

4.4.4-3 基础类型

Go 语言内置的基础类型包括:

  • 布尔类型

  • 数字类型,例如整数、浮点数、复数等

  • 字符串类型

编译器使用同一个结构来表示所有的基础类型,其定义如下:

// A Basic represents a basic type.
type Basic struct {
    kind BasicKind
    info BasicInfo
    name string
}

BasicInfo用来描述类型属性,例如该类型是否是数字类型,是否可排序。其定义如下:

type BasicInfo int

// Properties of basic types.
const (
    IsBoolean BasicInfo = 1 << iota
    IsInteger
    IsUnsigned
    IsFloat
    IsComplex
    IsString
    IsUntyped

    IsOrdered   = IsInteger | IsFloat | IsString
    IsNumeric   = IsInteger | IsFloat | IsComplex
    IsConstType = IsBoolean | IsNumeric | IsString
)

BasicInfo用来标识类型的性质,对应的常量申明已经解释了其潜在用途。

BasicKind用来标识实际类别,例如Int32, Float64等;这里简单罗列几个以做示例:

// BasicKind describes the kind of basic type.
type BasicKind int

const (
    Invalid BasicKind = iota // type is invalid

    // predeclared types
    Bool
    // ... 省略其它类型
    Int8
    Complex128
    String
    UnsafePointer

    // types for untyped values
    UntypedBool
    UntypedInt
    UntypedRune
    UntypedFloat
    UntypedComplex
    UntypedString
    UntypedNil

    // aliases
    Byte = Uint8
    Rune = Int32
)

可以看出对于每一个基础类型,都有一个枚举类型与之对应,另外可以发现 go 中的byte与rune类型只是uint8与int32的类型别名。

这里稍稍多讲一下 Untypedxxx 这一组类型。go 的类型系统非常严格,如果一个表达式的类型是确定的,那么不管在什么场景下,编译器都不会做任何隐式的类型转换,例如如下代码:

var a int16
var b int32

c := a + b // 错误信息:invalid operation: mismatched types int16 and int32

在赋值语句c := a + b 中,a与b的类型都是确定的整数类型,而将 a 转换为 int32 也是安全的,但 go 并不会这么做。反观 Java 则比较灵活,其定义了一组类型的隐式转换规则,例如如下 Java 代码是合法的:

int a = 10;
float b = 1.0f;
String c = "Java";
String d = a + b + c; 

但是 go 也没有死板到无可救药,对于类型还不确定的表达式,go 会根据上下文来进行类型转换。但 go 中仅允许常量在声明时拥有不确定的类型,对应BasicKind中Untypedxxx的几类。

更准确地说,go 基础类型的字面量都是 untyped 的,例如 "golang", 12, 3.14 分别对应 UntypedString, UntypedInt, UntypedFloat. 当使用这些字面量进行操作,例如赋值、运算、作为参数传递时,编译器会根据上下文将其转换为目标类型。我们来看几个示例:

  • 常量声明有类型

const name string = "Golang"

"Golang" 类型为 UntypedString, 但是 name 的声明中指定类型为 string, 所以编译器在初始化 name 时会将 "Golang" 转换为 string 类型并且赋值给 name. 如果此时类型转换不匹配,则会报错:

const name string = 1.2 // 错误信息:cannot convert 1.2(untyped float constant) to string
  • 常量声明无类型

const name = "Golang"
const alias string = name

name 没有申明类型,所以其类型复用字面量 "Golang" 的类型 UntypedString; 而 alias 的类型是 string, name 的值赋给 alias 的时候编译器会将其转换为 string 类型

  • 根据常量 underlying type 进行转换

type Str string

const name = "Golang"
const alias Str = name

const owner string = "google"
const ownerAlias Str = owner // 错误信息:cannot use owner (constant "google" of type string) as Str value in constant declaration

这里我们声明了新的类型 Str, 其 underlying type 是 string, 通过对 alias 的赋值语句可以看出:编译器依然可以将 UntypedString 转换为 Str 类型。但 owner 被显示声明成了 string 类型,其与 Str 毕竟是不同的类型,所以 ownerAlias 的赋值语句中,编译器不会将 string 类型的 owner 隐式转换为 Str 类型。

  • 常量声明无类型

type Str string

var name = "Golang"
var alias Str = name // 错误信息:cannot use name (variable of type string) as Str value in variable declaration

上面我们提到 go 仅仅支持常量拥有不确定类型,所以即使变量 name 没有申明类型,但编译器会根据右边的值推测出一个具体的类型赋给 name, 这里 "Golang" 的类型是 UntypedString。UntypedString 只能被转换为 string 类型,所以对 alias 的赋值会报错。

Untyped 类型的转换规则很简单:数字类型低精度可以向高精度转换,除此之外只有相同的类型才能相互转换。

上一页4.4.4-2 类型接口下一页4.4.4-4 内置复合类型

最后更新于3年前

这有帮助吗?