go ``` cmd/compile: 类型集合未正确推导出用于类型转换的类型参数, ```

j1dl9f46  于 4个月前  发布在  Go
关注(0)|答案(9)|浏览(58)

你正在使用的Go版本是什么( go version )?
Go Playground, 1.21

这个问题在最新版本的发布中是否重现?

是的

你正在使用什么操作系统和处理器架构( go env )?

N/A, Go Playground

你做了什么?

https://go.dev/play/p/Jhx6YGrz21s

你期望看到什么?

convert()convert2() 都被编译器接受,并且在语义上是等价的。

你看到了什么?

convert2() 被编译器拒绝: cannot convert x (variable of type srcType constrained by string) to type dstType: cannot convert string (in srcType) to type []elType (in dstType)

细节

规范关于类型参数的类型转换如下所述:
此外,如果T或x的类型V是类型参数,那么如果以下任一条件适用,x也可以转换为类型T:

  • V和T都是类型参数,并且V的类型集中的每个类型的值都可以转换为T的类型集中的每个类型。

考虑以下代码:

type charLike interface{ byte | rune }

func convert2[elType charLike, srcType string, dstType []elType](x srcType) dstType {
	return dstType(x) // ERROR: cannot convert x (variable of type srcType constrained by string) to type dstType: cannot convert string (in srcType) to type []elType (in dstType)
}

在这里,srcType 的类型集应该是 string ,而 dstType 的类型集是 []rune|[]byte 。根据规范, string 可以转换为 []rune[]byte :
非常量值x可以在以下任何情况下转换为类型T:

  • ...
  • x是一个字符串,而T是一个字节或rune的切片。

因此,假设编译器能够正确推断出 dstType 的类型集,转换应该是允许的。
我能够找到的最简单的问题示例是:

func convert2[elType byte, dstType []elType](x string) dstType {
	return dstType(x) // ERROR: cannot convert x (variable of type string) to type dstType: cannot convert string to type []elType (in dstType)
}

消除elType的下一步使错误消失:

func convert2[dstType []byte](x string) dstType {
	return dstType(x)}

所以看起来编译器无法将 dstType 的类型集与 elType 中的间接性展平。
此外,@Merovius 能够构造出两个语义上等价的例子,暗示这个问题在某种程度上是特定于类型转换操作的:

vvppvyoh

vvppvyoh1#

dstType 的类型集合是 []elType ,而不是 []rune|[]byte (规范):
非接口类型项的类型集合是由该类型组成的集合。
给定 elType 是一个类型参数,我不确定这是否意味着转换操作应该被允许。直观上,很明显对于每个有效的 convert2 示例化,elType 将与 runebyte 相同,因此“直观”的类型集合是那个并集。但我不确定规范是否意味着实际的类型集合是那样的。
我构造的原始示例(因此也是我试图用它们来说明的重点)与上面给出的有所不同:
被拒绝的: https://go.dev/play/p/DDn-SFzglUz
被接受的: https://go.dev/play/p/ling0sjUz8r
特别注意,函数示例化 要求您明确指定 Dst 必须满足 stringLike (或在我的例子中的 S )。转换似乎确实相关,因为在转换不成立的情况下,示例化仍然成功。但我觉得这并不令人惊讶,考虑到它们使用了不同的条件。上面的转换条件已经给出,并且明确地用两个类型参数的类型集合来表述,而示例化条件则是关于类型参数满足约束的:
替换后,每个类型参数必须满足相应类型的约束(如果需要的话进行示例化)。否则示例化失败。
直观上,它们应该意味着同样的事情,但当我涉及到类型参数、类型参数和类型集合时,我对规范的具体措辞感到困惑。特别是,我不太清楚对于一个类型参数类型的类型 参数(抱歉,这可能是令人兴奋的解析)满足约束是什么意思。
我构造这些示例的原因是因为对我来说,它们暗示了问题具有类似于在约束中显式提及方法的限制的结构。就像

type C interface{ *strings.Reader | *bytes.Reader }
func F[R C](r R) { r.Read(nil) } // r.Read undefined (type R has no field or method Read)

是不允许的,但一旦我们将约束与 io.Reader 相交,就会变得允许:

type C interface { *strings.Reader | *bytes.Reader; io.Reader }
func F[R C](r R) { r.Read(nil) } // fine

所以示例开始工作,一旦我们将约束与其他应该由并集隐含的接口相交。

db2dz4w8

db2dz4w82#

根据当前规范,这不是一个bug。一个更简单的例子:

func jon[T byte](x string) []T {
	return []T(x) // error
}

同样适用于赋值:

func pet[A, B []byte](x A, y B){
	x = y // error: cannot use y as type A in assignment
	y = x // error: cannot use x as type B in assignment
}

(只是从 here 中举出的两个小例子。)
规则可能会在以后放宽,但我不知道这是否值得。

pkln4tw6

pkln4tw63#

dstType 的类型集是 []elType ,而不是 []rune|[]byte (规范):
非接口类型项的类型集是由该类型组成的集合。
好的观点。我认为如果复合类型上允许的操作集不依赖于它所组成的类型,那么这个声明就不会引起争议。而且这在很大程度上是正确的,我能想到的唯一例外是字符串切片转换。
然后我想问的是:规范是否打算保留类型参数和具体类型之间的差异?

fhity93d

fhity93d4#

是的,这很令人困惑——现在改变像[]elType这样的约束合法性已经太晚了,但也许我们可以为单例类型集添加一个验证检查(我还需要再多想一会儿)。

hwazgwia

hwazgwia5#

问题可能是关于什么类型的组合满足类型参数?
[]eltype应该满足[]byte | []rune,如果eltype满足byte | rune。
可能类似于类型推断中使用的逻辑,切片类型构造函数可以看作是类型集合域上的双射。
这里的转换可能是特殊情况,并且在通用约束检查算法中没有简单地实现。

envsm3lx

envsm3lx6#

@go101 仅供参考:您的示例作业与此问题无关:

func pet[A, B []byte](x A, y B){
	x = y // error: cannot use y as type A in assignment
	y = x // error: cannot use x as type B in assignment
}

类型参数是命名类型(规范)。命名类型始终不同于任何其他类型(规范)。永远不可能将一个命名类型的值(A)赋给另一个命名类型的变量(B)。

o75abkj4

o75abkj47#

对于这个问题,查看一些简单的示例可能会有所帮助。在非泛型代码中,以下是有效的:

type E byte
func f0(x []E) string { return string(x) }

这个转换是有效的,因为“x 是一个整数、字节切片或 rune 的类型,T 是一个字符串类型”,并且因为 []E 被认为是一个“字节切片”,实现(至关重要!)会查看 E 的底层类型来做出这个判断。这种情况一直是这样,而且可以说没有很好地规定。
泛型变体

func f1[T []byte](x T) string { return string(x) }

也是被接受的,因为从 T 到字符串的转换如果对 T 中的每个类型都是允许的,那么就只有一个类型:[]byte。同样,

func f2[T []E](x T) string    { return string(x) }

也是可以的(将 E 全局定义为 byte),因为这只是 f0f1 的组合。
泛型变体

func f3[E byte](x []E) string { return string(x) } // ERROR cannot convert x (variable of type []E) to type string

由于非显而易见的原因而失败——不过我认为这实际上只是一个错误:请注意 x 的类型是一个切片类型,它碰巧切片元素是一个类型参数。类型显然是一个切片,但它是字节切片吗?实现会查看 E 的底层类型,而该类型恰好是一个接口,而不是 byte ,因此失败了。如果我们转而查看核心类型,我们会发现它确实存在,且为 byte ,然后这段代码就会被接受。(事实上,只需在实现中将正确的 under 调用更改为 coreType 就可以使此代码正常工作。)如上所述,我认为规范对于什么是 slice of bytes 没有明确说明,我们应该澄清这一点并使其在整个规范中保持一致(这也影响到诸如 appendcopy 等函数)。
同样,变体

func f4[T []E, E byte](x T) string { return string(x) }

在使用实现中的相同更改下也可以正常工作。
所以我的倾向是将其视为一个错误:首先,规范没有足够精确地说明;其次,实现在泛型和非泛型类型之间并不完全一致。
我怀疑(但我还没有百分之百确定)我们可以通过这种更改来实现这一目标,因为这将使迄今为止被拒绝的代码起作用。
此外,我们可以认为这些代码( f3f4 )应该可以工作,因为我们可以使用那些转换是有效的类型的示例化这些函数(也许在约束条件中适当地增强 ~ )。这可能是我们可以合理化这种更改的理由。这基本上遵循了 @Merovius 正在进行的关于类型集的论点;但有一个问题是如何准确地表述这一点。
最后,所有这些都没有解决 E 受到 byte | rune 约束的问题,就像原始问题中这个例子的情况一样,因为在那种情况下没有核心类型。我认为这是一个暂时的实现限制。回想一下最初的泛型提案,目标始终是允许对类型参数类型的值执行所有允许在各自类型集中的操作(假设我们真的意味着“示例化的”类型集,这是 Merovius 一直暗示的,我相信)。我们在很多情况下都在这样做,但肯定不是所有情况。在其他情况下逃脱的方法是求助于核心类型。这肯定是我们希望随着时间的推移澄清的事情之一,因为这可能有助于摆脱“核心类型”的概念,从而简化规范和语言(以更复杂的实现为代价)。

ctrmrzij

ctrmrzij8#

后续:规范中没有明确定义"字节片段"(或"符文片段"),但过去的例子被添加以明确其含义:

type myByte byte
string([]myByte{'w', 'o', 'r', 'l', 'd', '!'})       // "world!"

因此,如果 E 的底层类型是 byte,则 []E 是 "字节片段"。

相关问题