Skip to content

[WIP] js: tighten up internalize and externalize #618

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
wants to merge 1 commit into from

Conversation

myitcv
Copy link
Member

@myitcv myitcv commented Apr 6, 2017

!! DO NOT REVIEW !!

NEEDS REBASE AFTER #687 IS MERGED

Status

Step 1 and 2 (see below) review.

Description

A work-in-progress PR to complement the discussion in #617

Tighten up $internalize and $externalize such that:

  • zero val translation of undefined and null is well defined
  • the domain of valid values for a given target type is well defined
  • runtime errors are thrown for invalid values

See impact of this PR on honnef.co/go/js/dom in this comparison

Documentation

Proposed human-readable definitions of $internalize and $externalize can be seen here.

Scope of $internalize testing

The domain of source values we need to consider is:

  • null
  • undefined (there is no need to distinguish between a field/variable being explicitly assigned the value of undefined and it being so by virtue of not having been initialised)
  • Primitive and non-primitive values (see MDN)

Overall PR TODO list

  1. Agree on structure of code, tests and docs for a couple of types, e.g. string and bool
  2. Work out and fix reflection (manifests via TestReflection failing in js tests)
  3. Complete revised implementation of $internalize and $externalize for all types to allow for testing that code in real-life code bases (not just tests)
  4. Finish docs for all types
  5. Tighten up wording of *js.Object-special struct types in js package (unless we work this into the tables for $internalize and $externalize
  6. Document ways in which you can fallback to Javascript "loose" type conversions

Consideration for later PRs

  • Splitting $internalize and $externalize into separate, smaller type-specific functions
  • Fixing the fact that null and undefined do not internalize as (*T)(nil) for a *js.Object-special struct type T

@myitcv myitcv changed the title [WIP] fixes for internalize and zero val translation of undefined and null [WIP] Tighten up internalize for zero val translation of undefined and null Apr 6, 2017
@myitcv myitcv force-pushed the internalize_zero_val branch from 0bbcb74 to a8c3aed Compare April 7, 2017 12:20
js/js_test.go Outdated
type S struct {
*js.Object

string `js:"string"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a test of internalization logic (as I understand from our conversation in #617), I would suggest rewriting it to avoid using js struct field tags. I think it's simpler to use Object.String method, it's lower level and more explicit, making it more clear what the test is testing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shurcooL I think for completeness we'll need to test String() as well, but in order that we can cover all Go types I think we need to follow the struct field tags approach... unless I'm missing something?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but in order that we can cover all Go types I think we need to follow the struct field tags approach...

Are methods like String, Float, Bool Uint64, etc., not sufficient? What's not possible without struct field tags?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the types that do not have corresponding methods on Object. e.g. the specific numeric types like int8.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can Interface().(int8) be done for those, or is that not equivalent?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Interface() is another test case altogether because it uses the Javascript type of the value to decide which Go type to resolve to. So in the context of this discussion, I suspect use of Interface() wouldn't actually be a problem per se.

What you've proposed transpiles to:

$assertType($internalize(o.A, $emptyInterface), $Int8)

So we still need to have test cases for all the Go types... because it's $internalize that we're ultimately testing here.

Copy link
Member

@dmitshur dmitshur Apr 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks. Then it's okay to use the struct field tags, but my request is to document that what's being tested is internalize behavior, not the struct field tag behavior.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see; 100% agree on that point.

@myitcv myitcv force-pushed the internalize_zero_val branch from a8c3aed to 1b130f5 Compare April 8, 2017 16:39
@myitcv myitcv changed the title [WIP] Tighten up internalize for zero val translation of undefined and null [WIP] Tighten up internalize Apr 8, 2017
@myitcv myitcv force-pushed the internalize_zero_val branch from 1b130f5 to 0d5cce0 Compare April 8, 2017 16:45
@myitcv myitcv force-pushed the internalize_zero_val branch 3 times, most recently from d23fda2 to 93b99d9 Compare July 29, 2017 18:29
@myitcv myitcv changed the title [WIP] Tighten up internalize [WIP] Tighten up internalize and externalize Jul 29, 2017
//
// When values pass from Go to Javascript, a process known as externalization,
// the following conversion table is applied:
//
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Table missing?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See next line.

js/js.go Outdated
//
// Externalization
//
// When values pass from Go to Javascript, a process known as externalization,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incomplete sentence

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @flimzy - I should have mentioned this entire PR is still very much WIP.

In particular, I haven't even started on the $externalize side of things. Had some fairly interesting edge cases on the $internalize side of things

@myitcv
Copy link
Member Author

myitcv commented Jul 30, 2017

@flimzy @shurcooL - thanks both for taking a look at this.

I should have been clearer in my update message in #617 because I value your time... This is still WIP so I will give you a shout when it's essentially "ready" for review. There are still too many rough edges.

My plan is to get a couple of types (probably string and bool) done for both $internalize and $externalize and then get some feedback on how things look, from code and tests through to docs etc.

Just wanted to post an update to confirm I'm still "on" this, albeit after a significant period of inactivity!

@dmitshur
Copy link
Member

I should have been clearer in my update message in #617 because I value your time... This is still WIP so I will give you a shout when it's essentially "ready" for review. There are still too many rough edges.

No problem.

I suggest using Go code review convention for achieving this, put a "DO NOT REVIEW" text anywhere in the PR description to signal that. (See https://groups.google.com/d/msg/golang-dev/YU56eKYiXJg/K1CkXo7Iy0gJ.)

@myitcv myitcv changed the title [WIP] Tighten up internalize and externalize Tighten up internalize and externalize Jul 30, 2017
@myitcv
Copy link
Member Author

myitcv commented Jul 30, 2017

I suggest...

Done. I've switched from using my [WIP] convention in the title to your suggestion in the PR description.

Thanks again

@myitcv myitcv force-pushed the internalize_zero_val branch 3 times, most recently from f9d8435 to 517a713 Compare August 22, 2017 13:07
@myitcv
Copy link
Member Author

myitcv commented Aug 22, 2017

@shurcooL @flimzy et al.

Given there's quite a lot to work through in this PR, I've updated the description in this PR with, amongst other things, the following:

  • Status
  • Overall PR TODO list

Would welcome your thoughts on the TODO list, in terms of content but also approach (i.e. using the TODO list as a means of communicating how I plan to go about things).

At this point I think I've got to the end of step 1, hence am marking this for a review.

All thoughts/feedback welcomed.

Thanks

@myitcv
Copy link
Member Author

myitcv commented Aug 22, 2017

Worth pointing out that at this stage, not least because of point 2 from the TODO list, I'm not expecting the build to pass.

// | bool | none | null | false |
// | | | undefined | false |
// | | | false † | false |
// | | | new Boolean(false) † | false |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth including true examples here, for the sake of completeness, similar to the non-zero value examples for strings?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would end up adding two lines: true and new Boolean(true) - hence why I opted for the † callout.

@@ -467,7 +467,7 @@ func TestCallWithNull(t *testing.T) {
func TestReflection(t *testing.T) {
o := js.Global.Call("eval", "({ answer: 42 })")
if reflect.ValueOf(o).Interface().(*js.Object) != o {
t.Fail()
t.Fatal()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, Fatal() is for reporting a failure message. Maybe FailNow() would be more semantically correct?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it not possible to include a helpful error message in the t.Fatal call? If possible, that's better.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest I was being a little lazy here in just flipping the existing t.Fail() to t.Fatal() to help with debugging step 2 from the TODO.

I'm sure we could add helpful messages to the t.Fatal() calls... because the existing t.Fail() calls are equally lossy. Could even be something we do in another PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's usually easier to do these minor things as you're working in a relevant context than later. But this is completely up to you, and not critical for getting the PR into a mergeable state.

fieldName := "string"
zero := ""

s := &Internalize{Object: js.Global.Get("Object").New()}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stylistic note: I wonder if a table-based test would make sense here? It may make the tests more readable, reduce the need for the // *** comment blocks, and if coupled with t.Run(...) calls for each test, could make the output more readable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You raise a good point. I half considered this myself, but postponed giving it much thought until we've confirmed we're actually testing the right stuff... at which point extracting to table tests should be relatively easy.

The little thought I did give it however amount to the following: I'm not convinced we can actually use table tests. We need to address specific fields of the Internalize struct and do so in such a way that the gopherjs compiler sees us doing so and compiles to the corresponding Javascript. Put another way, the code we write in these tests needs to correspond exactly to the code that will be written in the wild.

Therefore the one approach I have considered fairly seriously is code generating the tests. Because they do follow a formula that's for sure.

But as I said, I haven't given this much time/though so would very much welcome any suggestions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@flimzy just pushed up a code-generated version of the tests which seems to work well. Let me know what you think.

@myitcv
Copy link
Member Author

myitcv commented Aug 25, 2017

Just pushed up a new commit per #617 (comment)

@myitcv myitcv force-pushed the internalize_zero_val branch 5 times, most recently from 0a359b9 to 19bf589 Compare August 27, 2017 22:28
@myitcv myitcv changed the title Tighten up internalize and externalize js: tighten up internalize and externalize Apr 2, 2018
@myitcv myitcv force-pushed the internalize_zero_val branch from dabda69 to ab5ceb8 Compare April 20, 2018 11:25
@myitcv myitcv changed the title js: tighten up internalize and externalize [WIP] js: tighten up internalize and externalize Apr 20, 2018
@flimzy
Copy link
Member

flimzy commented Dec 28, 2021

Nearly 4 years later, @nevkontakte and I have decided to close this PR. We like the effort here, and some of it will be very informative if we address these issues in the future, but with limited bandwidth, and the author having left GopherJS development, we feel this PR is at a dead end.

@flimzy flimzy closed this Dec 28, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants