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

BREAKING: Convert the unrestricted keyword into an extended attribute #857

Conversation

ExE-Boss
Copy link
Contributor

@ExE-Boss ExE-Boss commented Mar 18, 2020

Pre‑requisite for #843 (PR #856).

I decided to go with [UnrestrictedFloat] instead of [LegacyUnrestricted], as the latter implies that it applies to non‑floating‑point types as well.

That and there are valid non‑legacy reasons to allow non‑finite floating point values, specifically for Math APIs.

After #857 (comment), I’ve changed it to just be [Unrestricted].

Depends on:


Preview | Diff

@ExE-Boss ExE-Boss changed the title feat: Convert the unrestricted keyword into an extended attribute refactor: Convert the unrestricted keyword into an extended attribute Mar 18, 2020
@ExE-Boss ExE-Boss changed the title refactor: Convert the unrestricted keyword into an extended attribute BREAKING: Convert the unrestricted keyword into an extended attribute Mar 18, 2020
@annevk
Copy link
Member

annevk commented Mar 18, 2020

I don't think that rationale holds. [AllowShared] only applies to certain types. I think we should just call this [Unrestricted] and only have it apply to float types.

index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
@ExE-Boss ExE-Boss requested a review from annevk March 19, 2020 14:44
@annevk
Copy link
Member

annevk commented Mar 20, 2020

As a high-level comment, I suspect that before merging this we should also have a PR against HTML and perhaps some infrastructure PRs? I'm not entirely sure what will break if we make this kind of change. Are unknown extended attributes generally fine?

@saschanaz
Copy link
Member

Are unknown extended attributes generally fine?

ReSpec will throw when unknown extattrs appear.

@domenic
Copy link
Member

domenic commented Mar 20, 2020

Should we get buy-in from various bindings teams that they are interested in this churn?

@annevk
Copy link
Member

annevk commented Mar 20, 2020

We should. If it helps, I think it's worth doing as the main reason we have unrestricted is because extended attributes were not type-bound back in the day. Moving to JavaScript-aligned type names for floats and integers will be similar churn, but also well worth doing readability-wise. (E.g., that IDL's byte is not Infra's byte is super confusing.)

index.bs Outdated Show resolved Hide resolved
@ExE-Boss ExE-Boss requested a review from EdgarChen March 22, 2020 17:09
index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
@ExE-Boss ExE-Boss force-pushed the feat/extended-attribute/unrestricted-float branch from 3e26690 to ea24af3 Compare March 22, 2020 17:47
@annevk
Copy link
Member

annevk commented Mar 25, 2020

So in terms of implementer interest, I guess it would be good to hear from @yuki3 (Chrome), @EdgarChen (Firefox), @plinss (widlparser, somewhat critical for a lot of standards), @cdumez (WebKit; not entirely sure Chris is the right person, but he'd know the right person), @saschanaz (webidl2.js, somewhat critical for WPT).

Both for this change and the proposed renaming of numeric types in #843.

Then, if there's implementer support, before making this particular change I think we should at least patch HTML and do a quick grep to see what other specifications are impacted (WebGL?).

@ExE-Boss
Copy link
Contributor Author

ExE-Boss commented Mar 25, 2020

@annevk
Copy link
Member

annevk commented Mar 25, 2020

Thanks, I guess for specifications we really need @saschanaz onboard then as their bot can update specifications automatically (well, up to the point of review).

@saschanaz
Copy link
Member

I'll write an update when we get a consensus to land this.

@yuki3
Copy link

yuki3 commented Mar 25, 2020

Personally I like the both ideas, but I'd like to see the entire type system before moving on. For example, what do we do for USVString? [USV] DOMString? IMHO, the type system should be designed as a whole on top of the consistent concepts. Depending on what concept model of type system we have, I expect that we can discuss whether Int32Array or ArrayView<i32> is desirable for example.

In other words, I don't yet understand the (ultimate) goal of these proposals. If we'll keep USVString and DOMString separate, I don't understand why we'd like to turn unrestricted float into [UnrestrictedFloat] float.

@ExE-Boss
Copy link
Contributor Author

ExE-Boss commented Mar 25, 2020

We turn unrestricted float into [Unrestricted] float to simplify the parser.

Also, Int32Array is the name of the built‑in class in ECMAScript: https://tc39.es/ecma262/#sec-typedarray-objects

@domenic
Copy link
Member

domenic commented Mar 25, 2020

It's not clear there's anything "simplifying" about moving from working code to new code.

@saschanaz
Copy link
Member

I assume this is mainly to ease migration for #843, as we currently can't define a typedef with a space e.g. unrestricted f32.

@ExE-Boss
Copy link
Contributor Author

It’s not just that we can’t define a typedef with a space, it’s that unrestricted float32 and unrestricted float64 are syntax errors, so we have to use [Unrestricted] float32 and [Unrestricted] float64.

@saschanaz
Copy link
Member

it’s that unrestricted float32 and unrestricted float64 are syntax errors

I think that specific problem can be solved by modifying existing syntax, though?

@yuki3
Copy link

yuki3 commented Mar 26, 2020

As a thought experiment, we could turn everything into type-annotation:

                long →           [Integer32Bit] type
  unrestricted float → [UnrestrictedFloat32Bit] type
           USVString →              [USVString] type

As another thought experiment, we can turn all numeric types into a single type:

                long →           [Integer32Bit] numeric
  unrestricted float → [UnrestrictedFloat32Bit] numeric
           USVString →                    [USV] string

Why don't we do these things? Intuitively bad?

I expect that we can explain why we define X as a separate type and why we define [ExtAttr] X as an annotated type (not a separate type Y) based on clear guideline, policy, concept model, and/or rationale. At this moment, I can tell why unrestricted float should be [Unrestricted] float only from my intuitiveness, which I think we shouldn't rely on too much.

If this proposal were just a patchwork fix, I'm not convinced. Maybe it's better to fix typedef. Maybe it's expected that you cannot write unrestricted TypedefName because unrestricted is not a type modifier (as of now).

As long as we have consensus about the type system and it's clear what should be type and what should be annotated type, we should be able to discuss whether this proposal is aligned to the guideline/policy/etc or not.

Note that I'm not objecting to this proposal. My intuitiveness supports this way. But I also see lack of justification/rationale behind the proposal.

@domenic
Copy link
Member

domenic commented Mar 26, 2020

Very well put @yuki3.

First, I think it's worth separating primitives from all other types, for this discussion. I am reasonably happy with our non-primitive type system, e.g. dictionaries, enums, callbacks, sequences, records, promises, etc. So I think this is mostly about primitives. In particular: numeric types, boolean, DOMString, ByteString, USVString, symbol, and maybe any and object.

(Edited to add: I guess enums are a kind of string type, if you think about it. Hmm!)

Of those, numeric types and string types are basically what we're discussing. The rest are straightforward mappings to JS primitives.

One potential framework we could work toward is saying that types should correspond to JavaScript types, and extended attributes should modify the conversion algorithms. This would argue for turning all numeric types into a single type. This would have a benefit of corresponding IDL better to JS-ecosystem things like TypeScript or MDN documentation. However, for numeric types especially, this is more cumbersome for spec authors and implementers.

Another potential framework is to try to match some sort of consensus-type-system of C++/Rust, i.e. implementer languages. This is fuzzier, both because of differences between C++ and Rust, and because of differences in C++ dialects (e.g. different string types). For numeric types, I think the result would be similar to #843. For strings, its unclear which direction this would lean.

One issue with a C++/Rust type approach is that those languages don't have an idiomatic type for "double/float but not Infinity or NaN", and it's currently an explicit goal of Web IDL to make the easy path for spec authors not require thinking about those. That is, unrestricted behavior is opt-in in Web IDL, both currently and with this PR. Whereas if we took a principled C++/Rust approach, it would be opt-out.

A third possibility would be something in between. For example, we could try to be "JS first", but add conveniences or concessions to make spec-writing easier. It's super-common to want to accept only nonnegative integers, so forcing people to write [EnforceRange] [NonNegative] [Integer] number or something like that is not great. It's also historically common to add C++-like wrapping behavior for numbers by using unsigned long or long, even if this is not great practice. Maybe we just make some typedefs for the common cases, e.g. uint64 and int32, but we get rid of the rarer cases (like octet or byte or short), requiring those to be written long-hand or typedefed in the using spec.

(As an aside, people may enjoy my 2014-era proposal in #33 for reforming the numeric types. I don't really like it any more, but it illustrates somewhat similar thinking, of trying to be "JavaScript first" and not treating C++ boundaries like 232 or 0 as special. #33 (comment) is a 2019-era update on my thinking to be more spec-author friendly, but it is definitely not prinicipled in the way @yuki3 describes.)

@saschanaz
Copy link
Member

Whereas if we took a principled C++/Rust approach, it would be opt-out.

IMO this part should be decided before landing #843, as it will be way harder to migrate from float32 / [Unrestricted] float32 to [Restricted] float32 / float32 while it should be relatively straightforward to migrate from float / unrestricted float.

@domenic
Copy link
Member

domenic commented Mar 26, 2020

Agreed @saschanaz. Which reminds me that I kind of forgot a conclusion to my post. Unfortunately the conclusion is basically pessimistic: I cannot think of any principled framework for this space which accomplishes the goals of:

  • Grounding our primitive type system in an existing one (either JS, or C++/Rust)
  • Avoiding an increase in spec authors/implementer usage complexity
    • Any JS-based primitive type system causes a great increase in such complexity
    • Any C++/Rust-based primitive type system increases complexity by requiring folks to remember to prohibit or handle NaN/Infinities.

Maybe there is some other guiding principle besides grounding our primitive type system in an existing one?

@saschanaz
Copy link
Member

BTW, my intuition says an extended attribute for a type is for slightly modifying the condition, e.g. [AllowShared] BufferSource still receives a buffer source but allows shared state. [Unrestricted] float meets this description, while [Integer] number is quite a lot restriction so doesn't meet it.

@yuki3
Copy link

yuki3 commented Mar 27, 2020

For numeric types, I'm thinking about the following suggestion from Domenic.

Another potential framework is to try to match some sort of consensus-type-system of C++/Rust, i.e. implementer languages.

  1. Define numeric types based on its memory size.
    int8, int16, int32, and int64 should be different types.
  2. float32 and int32, float64 and int64 should be different types because it's very different how to interpret their bit patterns.
  3. int32 and unsigned int32 should be different types because sign extension, equality comparison, etc. are different even if their bit patterns are the same.

It looks good/reasonable to me to put memory layout and bit pattern interpretation as key principles of the type system of primitive types. I agree that [Restricted] looks more natural and consistent with others. (We should choose a better name for [Restricted], though. For example, [DisallowNanAndInf] looks clearer to me.)

# I'm okay to add typedefs for [Restricted] float to make spec authors' lives easier.

@annevk
Copy link
Member

annevk commented Apr 22, 2020

I tried to come up with some things I value in IDL that I think largely overlap with prior points made here:

  • Types in implementations. If a systems language might want a different type, I think that's a good hint for IDL. (This might argue for splitting ArrayBuffer and SharedArrayBuffer, though not sure about typed arrays. It also defends the current decisions around strings.)
  • Clarity and readability. For example, I think the current float fails here a bit as the size is not clear and more importantly it's not clear that it's very different from other languages (by not accepting infinity and not-a-number). float32 and restricted-float64 (I'm not sure about restricted, but also not sure about basic/numeric alternatives) seem preferable. IDL is read a lot, the clearer it is at a glance what will happen the better.
  • Alignment with type terminology of Infra, JavaScript, and WebAssembly. (DOMString -> String or even string?) And also alignment with type terminology from other languages to the extent possible.
  • Ease-of-use. Common things should be straightforward. I think in specifications we also get a lot of easy-of-use through "automatic" mapping between IDL types and Infra. We haven't quite figured out what the story for numbers should be there, but Infra should probably not have as many types as IDL. (I'm thinking "floats" and "integers" for prose and the IDL boundary takes care of conversion.)

In the end some of these decisions will have to be tradeoffs, but as long as try to maximize the benefit for these different audiences we'll be fine I think.

@ExE-Boss ExE-Boss force-pushed the feat/extended-attribute/unrestricted-float branch from a375817 to 5659a2c Compare March 6, 2021 13:55
@TimothyGu
Copy link
Member

There was a lot of good discussion happening, but I think it's clear that the idea needs some more time to bake. I'll close this PR for now.

@bathos
Copy link
Contributor

bathos commented Nov 6, 2021

Very late to this, but in case it were to return:

Currently extended attributes are described like this:

...used to control how language bindings will handle those constructs...

And the extended attributes defined by Web IDL are described like this:

This specification defines a number of extended attributes that are applicable to the ECMAScript language binding, which are described in § 3.3 Extended attributes.

That description doesn’t outright preclude the possibility that the “built-in” EAs could also be applicable to other language bindings — but it’s notable that if so, it would not be due to anything the spec states on the matter. Certain EAs seem like they surely ought to have significance to other bindings, but the current definitions of EAs stay true to the text quoted above: all of their effects and associated algorithms are specific to ECMAScript. CrossOriginIsolated, for example, is not defined in terms of its abstract effects but its concrete effects on the initialization of Web IDL constructs in a new ECMAScript realm.

Most non-exposure-related EAS don’t have an “intuitive” applicability outside of JS though. Looking at the existing applicable-to-numeric-types EAs, would there be any reason to believe that [Clamp] or [EnforceRange] should have significance for other language bindings? Their defined behavior appears pretty specific to ES’s Number type. In new bindings for a language with a native u64 type, it would be a bit odd if setting a [Clamp] unsigned long long attribute to 0x20_0000_0000_0000 would mean it gets converted it to 0x1F_FFFF_FFFF_FFFF (assuming a generous interpretation of all of the algorithm steps that wouldn’t be coherent anyway).

To me this makes it seem a lot like unrestricted double isn’t double-but-annotated. The Web IDL types proper have different value ranges — it’s not just about the behaviors realized at the boundary between ES and Web IDL.

This PR does (or would have) addressed this since this sentence is unqualified by anything specific to ES:

When defined with the [Unrestricted] extended attribute, the float type additionally corresponds to the single-precision 32 bit IEEE 754 non-finite, and special "not a number" values (NaNs).

If this is still bubbling, I’d suggest that having a clearer model of the intended scope of EA effects first would be helpful, and that the EA introduction would need to be updated to make it explicit that some, like [Unrestricted], are not binding-specific. However I also suspect this wouldn’t be a positive change and that it’s actually desirable for EAs, at least those applicable to types, to remain controls over binding-specific effects.

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

Successfully merging this pull request may close these issues.

8 participants