Skip to content

Commit

Permalink
go/types, types2: factor out type matching in binary operations
Browse files Browse the repository at this point in the history
Change-Id: Ica61698b6ba00687dcd133245bfc3d87808c7bca
Reviewed-on: https://go-review.googlesource.com/c/go/+/496096
Run-TryBot: Robert Griesemer <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
Auto-Submit: Robert Griesemer <[email protected]>
Reviewed-by: Robert Findley <[email protected]>
Reviewed-by: Robert Griesemer <[email protected]>
  • Loading branch information
griesemer authored and gopherbot committed May 18, 2023
1 parent 44b8f39 commit 72f448c
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 106 deletions.
116 changes: 63 additions & 53 deletions src/cmd/compile/internal/types2/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -815,59 +815,9 @@ func (check *Checker) binary(x *operand, e syntax.Expr, lhs, rhs syntax.Expr, op
return
}

// mayConvert reports whether the operands x and y may
// possibly have matching types after converting one
// untyped operand to the type of the other.
// If mayConvert returns true, we try to convert the
// operands to each other's types, and if that fails
// we report a conversion failure.
// If mayConvert returns false, we continue without an
// attempt at conversion, and if the operand types are
// not compatible, we report a type mismatch error.
mayConvert := func(x, y *operand) bool {
// If both operands are typed, there's no need for an implicit conversion.
if isTyped(x.typ) && isTyped(y.typ) {
return false
}
// An untyped operand may convert to its default type when paired with an empty interface
// TODO(gri) This should only matter for comparisons (the only binary operation that is
// valid with interfaces), but in that case the assignability check should take
// care of the conversion. Verify and possibly eliminate this extra test.
if isNonTypeParamInterface(x.typ) || isNonTypeParamInterface(y.typ) {
return true
}
// A boolean type can only convert to another boolean type.
if allBoolean(x.typ) != allBoolean(y.typ) {
return false
}
// A string type can only convert to another string type.
if allString(x.typ) != allString(y.typ) {
return false
}
// Untyped nil can only convert to a type that has a nil.
if x.isNil() {
return hasNil(y.typ)
}
if y.isNil() {
return hasNil(x.typ)
}
// An untyped operand cannot convert to a pointer.
// TODO(gri) generalize to type parameters
if isPointer(x.typ) || isPointer(y.typ) {
return false
}
return true
}
if mayConvert(x, &y) {
check.convertUntyped(x, y.typ)
if x.mode == invalid {
return
}
check.convertUntyped(&y, x.typ)
if y.mode == invalid {
x.mode = invalid
return
}
check.matchTypes(x, &y)
if x.mode == invalid {
return
}

if isComparison(op) {
Expand Down Expand Up @@ -936,6 +886,66 @@ func (check *Checker) binary(x *operand, e syntax.Expr, lhs, rhs syntax.Expr, op
// x.typ is unchanged
}

// matchTypes attempts to convert any untyped types x and y such that they match.
// If an error occurs, x.mode is set to invalid.
func (check *Checker) matchTypes(x, y *operand) {
// mayConvert reports whether the operands x and y may
// possibly have matching types after converting one
// untyped operand to the type of the other.
// If mayConvert returns true, we try to convert the
// operands to each other's types, and if that fails
// we report a conversion failure.
// If mayConvert returns false, we continue without an
// attempt at conversion, and if the operand types are
// not compatible, we report a type mismatch error.
mayConvert := func(x, y *operand) bool {
// If both operands are typed, there's no need for an implicit conversion.
if isTyped(x.typ) && isTyped(y.typ) {
return false
}
// An untyped operand may convert to its default type when paired with an empty interface
// TODO(gri) This should only matter for comparisons (the only binary operation that is
// valid with interfaces), but in that case the assignability check should take
// care of the conversion. Verify and possibly eliminate this extra test.
if isNonTypeParamInterface(x.typ) || isNonTypeParamInterface(y.typ) {
return true
}
// A boolean type can only convert to another boolean type.
if allBoolean(x.typ) != allBoolean(y.typ) {
return false
}
// A string type can only convert to another string type.
if allString(x.typ) != allString(y.typ) {
return false
}
// Untyped nil can only convert to a type that has a nil.
if x.isNil() {
return hasNil(y.typ)
}
if y.isNil() {
return hasNil(x.typ)
}
// An untyped operand cannot convert to a pointer.
// TODO(gri) generalize to type parameters
if isPointer(x.typ) || isPointer(y.typ) {
return false
}
return true
}

if mayConvert(x, y) {
check.convertUntyped(x, y.typ)
if x.mode == invalid {
return
}
check.convertUntyped(y, x.typ)
if y.mode == invalid {
x.mode = invalid
return
}
}
}

// exprKind describes the kind of an expression; the kind
// determines if an expression is valid in 'statement context'.
type exprKind int
Expand Down
116 changes: 63 additions & 53 deletions src/go/types/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -797,59 +797,9 @@ func (check *Checker) binary(x *operand, e ast.Expr, lhs, rhs ast.Expr, op token
return
}

// mayConvert reports whether the operands x and y may
// possibly have matching types after converting one
// untyped operand to the type of the other.
// If mayConvert returns true, we try to convert the
// operands to each other's types, and if that fails
// we report a conversion failure.
// If mayConvert returns false, we continue without an
// attempt at conversion, and if the operand types are
// not compatible, we report a type mismatch error.
mayConvert := func(x, y *operand) bool {
// If both operands are typed, there's no need for an implicit conversion.
if isTyped(x.typ) && isTyped(y.typ) {
return false
}
// An untyped operand may convert to its default type when paired with an empty interface
// TODO(gri) This should only matter for comparisons (the only binary operation that is
// valid with interfaces), but in that case the assignability check should take
// care of the conversion. Verify and possibly eliminate this extra test.
if isNonTypeParamInterface(x.typ) || isNonTypeParamInterface(y.typ) {
return true
}
// A boolean type can only convert to another boolean type.
if allBoolean(x.typ) != allBoolean(y.typ) {
return false
}
// A string type can only convert to another string type.
if allString(x.typ) != allString(y.typ) {
return false
}
// Untyped nil can only convert to a type that has a nil.
if x.isNil() {
return hasNil(y.typ)
}
if y.isNil() {
return hasNil(x.typ)
}
// An untyped operand cannot convert to a pointer.
// TODO(gri) generalize to type parameters
if isPointer(x.typ) || isPointer(y.typ) {
return false
}
return true
}
if mayConvert(x, &y) {
check.convertUntyped(x, y.typ)
if x.mode == invalid {
return
}
check.convertUntyped(&y, x.typ)
if y.mode == invalid {
x.mode = invalid
return
}
check.matchTypes(x, &y)
if x.mode == invalid {
return
}

if isComparison(op) {
Expand Down Expand Up @@ -921,6 +871,66 @@ func (check *Checker) binary(x *operand, e ast.Expr, lhs, rhs ast.Expr, op token
// x.typ is unchanged
}

// matchTypes attempts to convert any untyped types x and y such that they match.
// If an error occurs, x.mode is set to invalid.
func (check *Checker) matchTypes(x, y *operand) {
// mayConvert reports whether the operands x and y may
// possibly have matching types after converting one
// untyped operand to the type of the other.
// If mayConvert returns true, we try to convert the
// operands to each other's types, and if that fails
// we report a conversion failure.
// If mayConvert returns false, we continue without an
// attempt at conversion, and if the operand types are
// not compatible, we report a type mismatch error.
mayConvert := func(x, y *operand) bool {
// If both operands are typed, there's no need for an implicit conversion.
if isTyped(x.typ) && isTyped(y.typ) {
return false
}
// An untyped operand may convert to its default type when paired with an empty interface
// TODO(gri) This should only matter for comparisons (the only binary operation that is
// valid with interfaces), but in that case the assignability check should take
// care of the conversion. Verify and possibly eliminate this extra test.
if isNonTypeParamInterface(x.typ) || isNonTypeParamInterface(y.typ) {
return true
}
// A boolean type can only convert to another boolean type.
if allBoolean(x.typ) != allBoolean(y.typ) {
return false
}
// A string type can only convert to another string type.
if allString(x.typ) != allString(y.typ) {
return false
}
// Untyped nil can only convert to a type that has a nil.
if x.isNil() {
return hasNil(y.typ)
}
if y.isNil() {
return hasNil(x.typ)
}
// An untyped operand cannot convert to a pointer.
// TODO(gri) generalize to type parameters
if isPointer(x.typ) || isPointer(y.typ) {
return false
}
return true
}

if mayConvert(x, y) {
check.convertUntyped(x, y.typ)
if x.mode == invalid {
return
}
check.convertUntyped(y, x.typ)
if y.mode == invalid {
x.mode = invalid
return
}
}
}

// exprKind describes the kind of an expression; the kind
// determines if an expression is valid in 'statement context'.
type exprKind int
Expand Down

0 comments on commit 72f448c

Please sign in to comment.