Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

go/types: inconsistent handling of untyped expressions #13061

Open
mdempsky opened this issue Oct 27, 2015 · 10 comments

Comments

Projects
None yet
5 participants
@mdempsky
Copy link
Member

commented Oct 27, 2015

Currently if you type check this code with go/types:

var (
    _ int = 0
    _ = int(0)
    _ *int = nil
    _ = (*int)(nil)
)

both 0 expressions will have type "int", whereas the first nil expression will have type "untyped nil", and the second will have type "*int". This seems at the least inconsistent. See http://play.golang.org/p/cw8Ldz1U5D

My expectation was that the 0s would type check as "untyped int" and the nils would type check as "untyped nil". The current behavior of rewriting the type of untyped expressions seems to have two negative consequences that I've noticed trying to use go/types:

  1. It makes it difficult to identify useless conversion operations; i.e., expressions T(x) where x is already of type T. Expressions like int(0) will trigger as false positives.
  2. It causes types.TypeAndValue.IsNil to return false for the nil subexpression in (*int)(nil), because it doesn't have the type "untyped nil" anymore.

It also seems inconsistent with conversions of already typed expressions, where they're not rewritten.

However, I notice api_test.go has a bunch of tests that seem to explicitly test for this behavior, so it seems intentional?

CC @griesemer

@griesemer

This comment has been minimized.

Copy link
Contributor

commented Oct 27, 2015

@mdempsky It is definitively intentional - untyped constants are given their "inferred" type so that a backend - which needs to materialize the constants - knows of what type and size a constant is.

My guess would have been - for the example above - that the types would be int, untyped int, *nil, and untyped nil, but I need to look into the logic again.

I suspect the reason for the inconsistency is that nil is not considered a constant and thus perhaps is handled in a different code path.

@griesemer griesemer self-assigned this Oct 27, 2015

@griesemer griesemer added this to the Go1.6 milestone Oct 27, 2015

@mdempsky

This comment has been minimized.

Copy link
Member Author

commented Oct 27, 2015

I'd think the backend could determine the needed type and size for materializing the constants some other way (e.g., looking at the type of the variable they're being assigned to)? But at least if 0 in int(0) and nil in (*int)(nil) became "untyped int" and "untyped nil", respectively, like you suggest, that would help my immediate problem of wanting to identify unnecessary conversions.

@mdempsky

This comment has been minimized.

Copy link
Member Author

commented Oct 29, 2015

I took a stab at this, but discovered the trivial fix (just don't propagate types to explicit conversion arguments) causes a regression on this test case in test/fixedbugs/bug193.go, because it no longer detects the expected errors:

func main() {
        s := uint(10)
        ss := 1 << s
        y1 := float64(ss)
        y2 := float64(1 << s) // ERROR "shift"
        y3 := string(1 << s)  // ERROR "shift"
        _, _, _, _, _ = s, ss, y1, y2, y3
}

I'm actually rather surprised to find out the above is not valid Go (whereas float64(1 << uint(10)) is valid), but it seems to be in the spec:

If the left operand of a non-constant shift expression is an untyped constant, it is first converted to the type it would assume if the shift expression were replaced by its left operand alone.

Hrm.

@griesemer

This comment has been minimized.

Copy link
Contributor

commented Oct 29, 2015

@mdempsky yes, shifts are tricky business . i'll take care of this (let me know if this is blocking you)

@mdempsky

This comment has been minimized.

Copy link
Member Author

commented Oct 29, 2015

Nope, not blocking me. I was just interested in better understanding the problem.

@rsc rsc modified the milestones: Unplanned, Go1.6 Nov 30, 2015

@griesemer

This comment has been minimized.

Copy link
Contributor

commented Feb 25, 2017

Investigate and decide what to do.

@griesemer griesemer modified the milestones: Go1.9, Unplanned Feb 25, 2017

@bradfitz

This comment has been minimized.

Copy link
Member

commented Jun 19, 2017

Ping Robert. You'd labeled this Go 1.9.

@mdempsky

This comment has been minimized.

Copy link
Member Author

commented Jun 19, 2017

I think this can be safely bumped to 1.10. Nothing changed during 1.9 with respect to this issue.

@mdempsky mdempsky modified the milestones: Go1.10, Go1.9 Jun 19, 2017

@rsc rsc modified the milestones: Go1.10, Go1.11 Nov 22, 2017

@griesemer

This comment has been minimized.

Copy link
Contributor

commented Dec 21, 2017

The reason for the inconsistent behavior is that nil is indeed handled specially, see go/types/expr.go, Checker.convertUntyped function, at the end:

case *Pointer, *Signature, *Slice, *Map, *Chan:
	if !x.isNil() {
		goto Error
	}
	// keep nil untyped - see comment for interfaces, above
	target = Typ[UntypedNil]

Removing the special case for nil (deleting the line target = Typ[UntypedNil]) will produce consistent output where all untyped values in this case get registered with their "target" type; i.e., the untyped nil we see would become *int as well.

According to the comment, it's important to record the untyped nil here for tools like vet. We could do the same for non-nil values but as @mdempsky noticed, this causes problems with shifts.

There may be a solution, still. Leaving open for now.

@griesemer

This comment has been minimized.

Copy link
Contributor

commented Dec 21, 2017

It does seem reasonable to record the untyped type for values that are explicitly converted. So we should definitively fix this.

@griesemer griesemer modified the milestones: Go1.11, Go1.12 Jun 7, 2018

@griesemer griesemer modified the milestones: Go1.12, Unplanned Dec 6, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.