cmd/compile: report more precise errors about untyped constants

Previously, we used a single "untyped number" type for all untyped
numeric constants. This led to vague error messages like "string(1.0)"
reporting that "1 (type untyped number)" can't be converted to string,
even though "string(1)" is valid.

This CL makes cmd/compile more like go/types by utilizing
types.Ideal{int,rune,float,complex} instead of types.Types[TIDEAL],
and keeping n.Type in sync with n.Val().Ctype() during constant
folding.

Thanks to K Heller for looking into this issue, and for the included
test case.

Fixes #21979.

Change-Id: Ibfea88c05704bc3c0a502a455d018a375589754d
Reviewed-on: https://go-review.googlesource.com/c/go/+/194019
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Matthew Dempsky 2019-09-06 16:05:36 -07:00
parent 51c8d969bd
commit e710a1fb2e
11 changed files with 112 additions and 63 deletions

View File

@ -5,7 +5,6 @@
package gc package gc
import ( import (
"cmd/compile/internal/types"
"cmd/internal/src" "cmd/internal/src"
) )
@ -15,15 +14,6 @@ import (
// the same name appears in an error message. // the same name appears in an error message.
var numImport = make(map[string]int) var numImport = make(map[string]int)
func idealType(typ *types.Type) *types.Type {
switch typ {
case types.Idealint, types.Idealrune, types.Idealfloat, types.Idealcomplex:
// canonicalize ideal types
typ = types.Types[TIDEAL]
}
return typ
}
func npos(pos src.XPos, n *Node) *Node { func npos(pos src.XPos, n *Node) *Node {
n.Pos = pos n.Pos = pos
return n return n

View File

@ -998,6 +998,11 @@ func setconst(n *Node, v Val) {
Xoffset: BADWIDTH, Xoffset: BADWIDTH,
} }
n.SetVal(v) n.SetVal(v)
if n.Type.IsUntyped() {
// TODO(mdempsky): Make typecheck responsible for setting
// the correct untyped type.
n.Type = idealType(v.Ctype())
}
// Check range. // Check range.
lno := setlineno(n) lno := setlineno(n)
@ -1030,26 +1035,31 @@ func setintconst(n *Node, v int64) {
func nodlit(v Val) *Node { func nodlit(v Val) *Node {
n := nod(OLITERAL, nil, nil) n := nod(OLITERAL, nil, nil)
n.SetVal(v) n.SetVal(v)
switch v.Ctype() { n.Type = idealType(v.Ctype())
default:
Fatalf("nodlit ctype %d", v.Ctype())
case CTSTR:
n.Type = types.Idealstring
case CTBOOL:
n.Type = types.Idealbool
case CTINT, CTRUNE, CTFLT, CTCPLX:
n.Type = types.Types[TIDEAL]
case CTNIL:
n.Type = types.Types[TNIL]
}
return n return n
} }
func idealType(ct Ctype) *types.Type {
switch ct {
case CTSTR:
return types.Idealstring
case CTBOOL:
return types.Idealbool
case CTINT:
return types.Idealint
case CTRUNE:
return types.Idealrune
case CTFLT:
return types.Idealfloat
case CTCPLX:
return types.Idealcomplex
case CTNIL:
return types.Types[TNIL]
}
Fatalf("unexpected Ctype: %v", ct)
return nil
}
// idealkind returns a constant kind like consttype // idealkind returns a constant kind like consttype
// but for an arbitrary "ideal" (untyped constant) expression. // but for an arbitrary "ideal" (untyped constant) expression.
func idealkind(n *Node) Ctype { func idealkind(n *Node) Ctype {

View File

@ -686,11 +686,21 @@ func typefmt(t *types.Type, flag FmtFlag, mode fmtMode, depth int) string {
} }
if int(t.Etype) < len(basicnames) && basicnames[t.Etype] != "" { if int(t.Etype) < len(basicnames) && basicnames[t.Etype] != "" {
name := basicnames[t.Etype] switch t {
if t == types.Idealbool || t == types.Idealstring { case types.Idealbool:
name = "untyped " + name return "untyped bool"
case types.Idealstring:
return "untyped string"
case types.Idealint:
return "untyped int"
case types.Idealrune:
return "untyped rune"
case types.Idealfloat:
return "untyped float"
case types.Idealcomplex:
return "untyped complex"
} }
return name return basicnames[t.Etype]
} }
if mode == FDbg { if mode == FDbg {

View File

@ -374,8 +374,6 @@ func (p *importReader) value() (typ *types.Type, v Val) {
p.float(&x.Imag, typ) p.float(&x.Imag, typ)
v.U = x v.U = x
} }
typ = idealType(typ)
return return
} }

View File

@ -1407,20 +1407,18 @@ func typecheck1(n *Node, top int) (res *Node) {
} }
// Determine result type. // Determine result type.
et := t.Etype switch t.Etype {
switch et {
case TIDEAL: case TIDEAL:
// result is ideal n.Type = types.Idealfloat
case TCOMPLEX64: case TCOMPLEX64:
et = TFLOAT32 n.Type = types.Types[TFLOAT32]
case TCOMPLEX128: case TCOMPLEX128:
et = TFLOAT64 n.Type = types.Types[TFLOAT64]
default: default:
yyerror("invalid argument %L for %v", l, n.Op) yyerror("invalid argument %L for %v", l, n.Op)
n.Type = nil n.Type = nil
return n return n
} }
n.Type = types.Types[et]
case OCOMPLEX: case OCOMPLEX:
ok |= ctxExpr ok |= ctxExpr
@ -1457,7 +1455,7 @@ func typecheck1(n *Node, top int) (res *Node) {
return n return n
case TIDEAL: case TIDEAL:
t = types.Types[TIDEAL] t = types.Idealcomplex
case TFLOAT32: case TFLOAT32:
t = types.Types[TCOMPLEX64] t = types.Types[TCOMPLEX64]
@ -2683,20 +2681,20 @@ func errorDetails(nl Nodes, tstruct *types.Type, isddd bool) string {
// e.g in error messages about wrong arguments to return. // e.g in error messages about wrong arguments to return.
func sigrepr(t *types.Type) string { func sigrepr(t *types.Type) string {
switch t { switch t {
default:
return t.String()
case types.Types[TIDEAL]:
// "untyped number" is not commonly used
// outside of the compiler, so let's use "number".
return "number"
case types.Idealstring: case types.Idealstring:
return "string" return "string"
case types.Idealbool: case types.Idealbool:
return "bool" return "bool"
} }
if t.Etype == TIDEAL {
// "untyped number" is not commonly used
// outside of the compiler, so let's use "number".
// TODO(mdempsky): Revisit this.
return "number"
}
return t.String()
} }
// retsigerr returns the signature of the types // retsigerr returns the signature of the types

View File

@ -334,14 +334,7 @@ func typeinit() {
maxfltval[TCOMPLEX128] = maxfltval[TFLOAT64] maxfltval[TCOMPLEX128] = maxfltval[TFLOAT64]
minfltval[TCOMPLEX128] = minfltval[TFLOAT64] minfltval[TCOMPLEX128] = minfltval[TFLOAT64]
// for walk to use in error messages types.Types[TINTER] = types.New(TINTER) // empty interface
types.Types[TFUNC] = functype(nil, nil, nil)
// types used in front end
// types.Types[TNIL] got set early in lexinit
types.Types[TIDEAL] = types.New(TIDEAL)
types.Types[TINTER] = types.New(TINTER)
// simple aliases // simple aliases
simtype[TMAP] = TPTR simtype[TMAP] = TPTR

View File

@ -53,6 +53,13 @@ func identical(t1, t2 *Type, cmpTags bool, assumedEqual map[typePair]struct{}) b
assumedEqual[typePair{t1, t2}] = struct{}{} assumedEqual[typePair{t1, t2}] = struct{}{}
switch t1.Etype { switch t1.Etype {
case TIDEAL:
// Historically, cmd/compile used a single "untyped
// number" type, so all untyped number types were
// identical. Match this behavior.
// TODO(mdempsky): Revisit this.
return true
case TINTER: case TINTER:
if t1.NumFields() != t2.NumFields() { if t1.NumFields() != t2.NumFields() {
return false return false

View File

@ -57,7 +57,7 @@ const (
TUNSAFEPTR TUNSAFEPTR
// pseudo-types for literals // pseudo-types for literals
TIDEAL TIDEAL // untyped numeric constants
TNIL TNIL
TBLANK TBLANK
@ -94,7 +94,6 @@ const (
// It also stores pointers to several special types: // It also stores pointers to several special types:
// - Types[TANY] is the placeholder "any" type recognized by substArgTypes. // - Types[TANY] is the placeholder "any" type recognized by substArgTypes.
// - Types[TBLANK] represents the blank variable's type. // - Types[TBLANK] represents the blank variable's type.
// - Types[TIDEAL] represents untyped numeric constants.
// - Types[TNIL] represents the predeclared "nil" value's type. // - Types[TNIL] represents the predeclared "nil" value's type.
// - Types[TUNSAFEPTR] is package unsafe's Pointer type. // - Types[TUNSAFEPTR] is package unsafe's Pointer type.
var Types [NTYPE]*Type var Types [NTYPE]*Type
@ -112,8 +111,6 @@ var (
Idealbool *Type Idealbool *Type
// Types to represent untyped numeric constants. // Types to represent untyped numeric constants.
// Note: Currently these are only used within the binary export
// data format. The rest of the compiler only uses Types[TIDEAL].
Idealint = New(TIDEAL) Idealint = New(TIDEAL)
Idealrune = New(TIDEAL) Idealrune = New(TIDEAL)
Idealfloat = New(TIDEAL) Idealfloat = New(TIDEAL)

View File

@ -19,7 +19,7 @@ func F() {
const x = 1 const x = 1
func G() { func G() {
switch t := x.(type) { // ERROR "cannot type switch on non-interface value x \(type untyped number\)" switch t := x.(type) { // ERROR "cannot type switch on non-interface value x \(type untyped int\)"
default: default:
} }
} }

View File

@ -0,0 +1,46 @@
// errorcheck
// Copyright 2019 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.
package p
func f() {
_ = bool("") // ERROR "cannot convert .. \(type untyped string\) to type bool"
_ = bool(1) // ERROR "cannot convert 1 \(type untyped int\) to type bool"
_ = bool(1.0) // ERROR "cannot convert 1 \(type untyped float\) to type bool"
_ = bool(-4 + 2i) // ERROR "cannot convert -4 \+ 2i \(type untyped complex\) to type bool"
_ = string(true) // ERROR "cannot convert true \(type untyped bool\) to type string"
_ = string(-1)
_ = string(1.0) // ERROR "cannot convert 1 \(type untyped float\) to type string"
_ = string(-4 + 2i) // ERROR "cannot convert -4 \+ 2i \(type untyped complex\) to type string"
_ = int("") // ERROR "cannot convert .. \(type untyped string\) to type int"
_ = int(true) // ERROR "cannot convert true \(type untyped bool\) to type int"
_ = int(-1)
_ = int(1)
_ = int(1.0)
_ = int(-4 + 2i) // ERROR "truncated to integer"
_ = uint("") // ERROR "cannot convert .. \(type untyped string\) to type uint"
_ = uint(true) // ERROR "cannot convert true \(type untyped bool\) to type uint"
_ = uint(-1) // ERROR "constant -1 overflows uint"
_ = uint(1)
_ = uint(1.0)
_ = uint(-4 + 2i) // ERROR "constant -4 overflows uint" "truncated to integer"
_ = float64("") // ERROR "cannot convert .. \(type untyped string\) to type float64"
_ = float64(true) // ERROR "cannot convert true \(type untyped bool\) to type float64"
_ = float64(-1)
_ = float64(1)
_ = float64(1.0)
_ = float64(-4 + 2i) // ERROR "truncated to real"
_ = complex128("") // ERROR "cannot convert .. \(type untyped string\) to type complex128"
_ = complex128(true) // ERROR "cannot convert true \(type untyped bool\) to type complex128"
_ = complex128(-1)
_ = complex128(1)
_ = complex128(1.0)
}

View File

@ -11,5 +11,5 @@ package main
func main() { func main() {
_ = copy(nil, []int{}) // ERROR "use of untyped nil" _ = copy(nil, []int{}) // ERROR "use of untyped nil"
_ = copy([]int{}, nil) // ERROR "use of untyped nil" _ = copy([]int{}, nil) // ERROR "use of untyped nil"
_ = 1 + true // ERROR "mismatched types untyped number and untyped bool" _ = 1 + true // ERROR "mismatched types untyped int and untyped bool"
} }