Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Type conversion errors #977

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

Closed
flimzy opened this issue Apr 10, 2020 · 7 comments · Fixed by #1220
Closed

Type conversion errors #977

flimzy opened this issue Apr 10, 2020 · 7 comments · Fixed by #1220

Comments

@flimzy
Copy link
Member

flimzy commented Apr 10, 2020

I've run into an unusual problem. In my real code, the result is inconsistent:

If I run my code once, it succeeds, but the second run, with no manual changes, produces the error:

$ gopherjs run ./foo/foo.go
gopherjs: Source maps disabled. Install source-map-support module for nice stack traces. See https://github.com/gopherjs/gopherjs#gopherjs-run-gopherjs-test.
2020-04-10 13:42:11.405 +0200 Central European Summer Time m=+0.083000001
2020-04-10 13:42:11
{13806968577655557952 83000001 0x1}
jonhall@hyaline:~/go/src/gitlab.com/FlashbackSRS/priv/fbrender$ gopherjs run ./foo/foo.go
foo/foo.go:14:14: cannot convert x (variable of type time.Time) to fb.Due

Running go mod vendor somehow "resets" the state, so that the next run will work again, but after one success, it's broken again.

Strange.

I have been able to reproduce a minimal test case, but it consistently exhibits the failure, so I'm not sure what's different. Full code is here: https://github.com/flimzy/timeconvertiontest

Summary:

Define a custom type Time in an imported package:

package foo

import "time"

type Time time.Time

Then try to convert from time.Time to the custom type:

package main

import (
    "time"
    "foo"
)

func main() {
    _ = foo.Time(time.Now())
}

When I build the code in the linked repository, using both Go (1.14) and GopherJS (1.12):

$ go build main.go
$ gopherjs build main.go
main.go:10:17: cannot convert time.Now() (value of type time.Time) to types.Time
@flimzy
Copy link
Member Author

flimzy commented Apr 10, 2020

I have been able to reproduce this all the way back to GopherJS 1.12-1, so this does not appear to be caused by using the new GOPHERJS_ROOT configuration, for what that's worth.

@flimzy
Copy link
Member Author

flimzy commented Apr 11, 2020

This one has me really stumped. I've attempted to go back to code I knew was working in the past, with an old version of GopherJS, and the problem is still occurring. I would think my local environment was somehow broken, but I first noticed the problem on GitLab-CI, within Docker, so it must be more than that.

Can anyone else reproduce this? I'd love a small vote of confidence that I'm not going insane.

@nevkontakte
Copy link
Member

nevkontakte commented Apr 16, 2020

I've tried to reproduce your issue at current master and I noticed an interesting behavior:

 $ gopherjs build github.com/flimzy/timeconvertiontest
$ gopherjs build github.com/flimzy/timeconvertiontest
main.go:10:17: cannot convert time.Now() (value of type time.Time) to types.Time
$ rm ~/go/pkg/linux_js/github.com/flimzy/timeconvertiontest/types.a
$ gopherjs build github.com/flimzy/timeconvertiontest
$ gopherjs build github.com/flimzy/timeconvertiontest
main.go:10:17: cannot convert time.Now() (value of type time.Time) to types.Time

Apparently the error occurs when trying to load cached version of github.com/flimzy/timeconvertiontest/types from the previous compilation pass, but everything works when building from scratch.

Edit: I've encountered a similar error when I implemented gopherjs integration with bazel, although in a more bizarre form, it claimed unable to convert time.Time to time.Time (or any other type from standard library) :) At the time I blamed it onto a lame way I handled building standard library, but perhaps it has a similar root. I'll try to investigate that bug again when I have a bit of time and post if I find anything useful here.

@flimzy
Copy link
Member Author

flimzy commented Apr 16, 2020

I've tried to reproduce your issue at current master and I noticed an interesting behavior:

Thank you for taking the time!

I've noticed a number of other gopherjs caching bugs, and am in the habit of clearing the cache before each build. That doesn't seem to be enough to make this work in my larger program (perhaps because the same import is included more than once, so hitting the cache is inevitable?)

@nevkontakte
Copy link
Member

My gut feeling is that the bug lays at the border between how gopherjs caches intermediate build results and how go/types checks for type equality. I suspect that go/types relies on pointer equality somewhere for type equality checks, but that gets disturbed by serializing/deserializing a library archive. I haven't verified this, though, so take it with a grain of salt.

@nevkontakte
Copy link
Member

I spent a bunch of time with the debugger and tracked down the error source.

How the error happens?

The failing check is test whether time.Time is assignable to types.Time. The reason why first and second passes behave differently is that "underlying type" for time.Time and types.Time looks different between runs.

The first time the underlying type refers to the struct in with a list of fields, and for those fields pkg points at time package.

(dlv) p T.underlying
go/types.Type(*go/types.Struct) *{
        fields: []*go/types.Var len: 3, cap: 3, [
                *(*"go/types.Var")(0xc0002ad950),
                *(*"go/types.Var")(0xc0002ad9a0),
                *(*"go/types.Var")(0xc00024c6e0),
        ],
        tags: []string len: 3, cap: 3, ["","",""],}
(dlv) p T.underlying.fields[0].pkg
*go/types.Package {
        path: "time",
        name: "time",
        scope: *go/types.Scope {
                parent: *(*"go/types.Scope")(0xc0000b6140),
                children: []*go/types.Scope len: 0, cap: 0, nil,
                elems: map[string]go/types.Object [...],
                pos: NoPos (0),
                end: NoPos (0),
                comment: "package \"time\"",
                isFunc: false,},
        complete: true,
        imports: []*go/types.Package len: 1, cap: 1, [
                *(*"go/types.Package")(0xc0003cd950),
        ],
        fake: false,}
(dlv) 

The second time, there is a subtle difference in that the fields are referencing github.com/flimzy/timeconvertiontest/types in the pkg field.

(dlv) p T.underlying
go/types.Type(*go/types.Struct) *{
        fields: []*go/types.Var len: 3, cap: 3, [
                *(*"go/types.Var")(0xc000367a90),
                *(*"go/types.Var")(0xc000367ae0),
                *(*"go/types.Var")(0xc000367b30),
        ],
        tags: []string len: 3, cap: 3, ["","",""],}
(dlv) p T.underlying.fields[0].pkg
*go/types.Package {
        path: "github.com/flimzy/timeconvertiontest/types",
        name: "types",
        scope: *go/types.Scope {
                parent: *(*"go/types.Scope")(0xc0000cc140),
                children: []*go/types.Scope len: 0, cap: 0, nil,
                elems: map[string]go/types.Object [...],
                pos: NoPos (0),
                end: NoPos (0),
                comment: "package \"github.com/flimzy/timeconvertiontest/types\"",
                isFunc: false,},
        complete: true,
        imports: []*go/types.Package len: 1, cap: 1, [
                *(*"go/types.Package")(0xc00021f2c0),
        ],
        fake: false,}

The type in both cases structurally the same, but the subtle difference (at least as I interpret it) is that in the first case fields say "I was originally defined in time package", and in the second case they say "I was originally defined in github.com/flimzy/timeconvertiontest/types package". It kind of forgets that the type was derived from time.Time and pretends that it was defined in from scratch.

Code relevant to this logic is located in https://github.com/golang/go/blob/master/src/go/types/conversions.go if anyone's interested.

What causes the error to happen?

Long story short, the problem seems to be rooted in golang.org/x/tools/go/gcexportdata package, which GopherJS uses to store type information for precompiled packages. When gcexportdata writes out type information for struct fields it writes the package that surrounds the struct type, not the one the field refers to: https://github.com/golang/tools/blob/0f592d2728bb767204b019bf926fbcd791267115/go/internal/gcimporter/iexport.go#L412.

There seems to be a complimentary bug when reading field information, where it just assumes the surrounding package: https://github.com/golang/tools/blob/0f592d2728bb767204b019bf926fbcd791267115/go/internal/gcimporter/iimport.go#L542. Unfortunately that means that even updating the write part to use f.Pkg() doesn't fix the problem :(

How to fix the problem?

Unfortunately, I've no idea. I don't think GopherJS is doing anything wrong here, the bug seems to be mostly localized to the gcexportdata package, but I couldn't find any specs of the format it uses, so I don't even know if it can preserve this data. Probably the next step would be to write a minimal reproduction sample and report a bug to the Go team but I'm a bit too tired to do that right now. If anyone's willing it would be great, if not I'll try to get around doing it some time later.

@nevkontakte
Copy link
Member

/cc @dmitshur in case he knows something useful about gcexportdata format.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants