You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Checking the [[ErrorData]] slot ensures it's an Error or an Error subclass.
However, when someone matches against, say, TypeError, we only want a TypeError to satisfy this.
Here are some options:
do a Get of name on the constructor, do a Get of the constructor on the value and a Get of name on that constructor, and compare them (most observable, least robust)
do a Get of constructor on the value, and SameValue to the constructor (better than the previous, but still relies on an observable and forgeable lookup)
add a new subclass-specific internal slot to NativeError and AggregateError, and check for it on the value (most robust, but adds a new capability that isn't already present)
functionhandleError(error){returnmatch(error){when(${MySyntaxError}): console.error(`Error occured with value: ${error.customProperty}`)...default: console.error('Unknown error!')};}
This code will act like it works just fine, until a normal SyntaxError gets passed in, at which point it'll go down the wrong branch and end up logging Error occurred with value: undefined. Is this a correct understanding? This seems like an unfortunate foot-gun is subclasses inherit the pattern-matching behaviors like this. The same applies for inheriting non-error classes as well, but that tends to be less common.
@Jack-Works what you wrote yesterday is option 1. However, option 3 also works cross-realm, and all options will work with proper subclasses, since they'll have the proper slots.
Everything that's not Error already will work, because a proper subclass will have the internal slot of the base. It's just that Error subclasses don't have a unique slot - just a slot shared with the base Error.
@theScottyJam with all three options, your subclass will be treated as a SyntaxError, since it will have the proper slots.
You are certainly correct that your subclass would have to define its own custom matcher if you wanted to be able to robustly match against your subclass instances.
Perhaps another choice would be to store new.target in an internal slot, and then access that - but then Error subclasses would no longer work cross-realm.
Perhaps another choice would be to store new.target in an internal slot, and then access that - but then Error subclasses would no longer work cross-realm.
Could there perhaps be a mix? If you're creating an instance of an Error, or of a built-in subclass of an error, then it uses option 3 and stores some value unique to that builtin-type in an internal slot, but if it's a userland subclass we store new.target?
I suppose we could do that. Each builtin subclass would check new.target, and if it was SameValue, it'd set the internal slot to the string name, and if not, it'd set it to new.target. That would work cross-realm with all builtins, and same-realm (the only option) with userland subclasses. It'd get a bit more complex to handle in Error itself, but it's doable.
Yeah, #3 is probably what I'd expect. It does indeed mean that subclasses will test as correct, but that's identical to how any other class's subclasses will work, so that seems expected - classes inherit their static members from their superclass as well, which will include the [Symbol.matcher] method. There is literally no way to avoid this without bypassing the class system entirely (some sort of registry?) which seems bad. (Subclassing is, itself, often bad, but that's something every coder needs to learn for themselves, apparently.)
Reacted by Jordan Harband and Darien Maillet Valentine
FWIW, as a rando developer, I feel that error type matching is one of the primary use cases for pattern matching.
As an example, I frequently write code at the API layer that converts domain errors to JSON responses, and that code is almost always something like "if the error is of this type, return this response, else if it's that type, return that response, ...". Pattern matching would be a big improvement for me here so long as I can easily differentiate different error types.
If the above example didn't work as-is in the final version of pattern matching, I'd personally be very, very sad.
I've put up #279, on top of #273, which combined use instanceof semantics whenever the original constructor was not a builtin. I strongly dislike instanceof semantics, but I can't think of a more appropriate choice, sadly.
And, to be clear, is that change going to just make subclasses of Error work, or will it allow subclasses of all built-in types? (From my reading, it looks like the former?)
I don't feel overly strong about other built-in types, since I know they're rarely subclasses and really shouldn't be, except that it would be nice for consistency sake to make them all behave the same when subclassed.
Also, I know that, for example, HTMLDivElement is a browser API and not part of the core of EcmaScript, but I suspect browser APIs will follow whatever pattern we set here with their built-in types. And, I know they actually encorage subclassing HTMLDivElement (and all other HTML element classes), as that's the only way to make custom elements. It would be nice if pattern-matching against one of those userland subclasses just worked as well.
@theScottyJam#273 only makes builtin error subclasses work. #279 would make userland subclasses of any builtin constructor (except the primitives) work.
I suspect browser APIs will follow whatever pattern we set here with their built-in types. [...] It would be nice if pattern-matching against [userland subclasses of HTMLElement] just worked as well.
Yep, Web IDL (at internally create a new object implementing interface) deliberately replicates the same observable behaviors as OrdinaryCreateFromConstructor, just using different spec terminology (because they don’t share the [[Intrinsics]] & %Name% syntax pattern). It seems likely that the [[NewTarget]] slot logic could/would end up incorporated similarly because it was expressly stated in the past that alignment was the intention.
HTML uses a special construct-algorithm override in place of the usual Web IDL steps via the [HTMLConstructor] extended attribute but delegates to the same at step 7.1. Because these constructors-when-used-as-super already have unique handling, there might be a good opportunity there to address the custom element scenario in particular with “automatic” subclass matching (esp. given the agent has privileged knowledge of whether instances are valid; there is effectively a kind of built-in branding for these subclasses). The only other commonly subclassed platform interface constructor I can think of is Event, but I don’t personally think implementing branded matchers is a big burden for that scenario — there’s already a need for special care along those lines when doing subclassing it.
@bathos i got some feedback that the slot approach won't likely be palatable to implementations, so i'm working on an alternative approach that walks the prototype, supports intrinsics from any realm, and gets .constructor to do so. Both approaches should theoretically Just Work with web subclasses as well - HTML would only have to do extra work if it wanted things to work cross-realm for web builtins.
Reacted by Darien Maillet Valentine and Scotty Jamison
Yup, the #279 approach will automatically work for WebIDL objects, in fact, and userland subclasses of HTMLElement, since it proto-walks - so long as you don't want to do anything fancy, when (${MyNewElement}): ... will Just Work.
It doesn't even require any new special-cases unless we specifically want cross-window versions of WebIDL-defined classes to also match (which we might, I dunno, but if we do it's a pretty easy tweak to WebIDL to duplicate what #279 is doing for all WebIDL-defined classes as well).
Activity
ljharb commentedon Jul 8, 2022
My preference is the third, because it's the most robust.
Userland subclasses, of course, can and should use a private field and
#brand in obj, which has the same semantics as option 3.Jack-Works commentedon Jul 8, 2022
Current algorithm (as I wrote yesterday):
[[ErrorData]]internal slot.value.constructorequalsthis, if so, it matchesvalue.constructor.nameequalsthis.name, if so, it matches.This algorithm automatically works on all sub-classes (even from the user land) of the Error class, and it also works cross-realm.
theScottyJam commentedon Jul 8, 2022
@ljharb With option 3, if I were to do this:
and then a user of my library tried to do this:
This code will act like it works just fine, until a normal SyntaxError gets passed in, at which point it'll go down the wrong branch and end up logging
Error occurred with value: undefined. Is this a correct understanding? This seems like an unfortunate foot-gun is subclasses inherit the pattern-matching behaviors like this. The same applies for inheriting non-error classes as well, but that tends to be less common.Jack-Works commentedon Jul 8, 2022
I'm worrying we need to consider all commonly subclassed features:
ljharb commentedon Jul 8, 2022
@Jack-Works what you wrote yesterday is option 1. However, option 3 also works cross-realm, and all options will work with proper subclasses, since they'll have the proper slots.
Everything that's not Error already will work, because a proper subclass will have the internal slot of the base. It's just that Error subclasses don't have a unique slot - just a slot shared with the base Error.
ljharb commentedon Jul 8, 2022
@theScottyJam with all three options, your subclass will be treated as a SyntaxError, since it will have the proper slots.
You are certainly correct that your subclass would have to define its own custom matcher if you wanted to be able to robustly match against your subclass instances.
Perhaps another choice would be to store
new.targetin an internal slot, and then access that - but then Error subclasses would no longer work cross-realm.theScottyJam commentedon Jul 8, 2022
Could there perhaps be a mix? If you're creating an instance of an Error, or of a built-in subclass of an error, then it uses option 3 and stores some value unique to that builtin-type in an internal slot, but if it's a userland subclass we store
new.target?ljharb commentedon Jul 8, 2022
I suppose we could do that. Each builtin subclass would check
new.target, and if it was SameValue, it'd set the internal slot to the string name, and if not, it'd set it tonew.target. That would work cross-realm with all builtins, and same-realm (the only option) with userland subclasses. It'd get a bit more complex to handle inErroritself, but it's doable.Jack-Works commentedon Jul 8, 2022
ljharb commentedon Jul 8, 2022
@Jack-Works yea, if we go with #265 (comment) they would all match as expected.
tabatkins commentedon Jul 11, 2022
Yeah, #3 is probably what I'd expect. It does indeed mean that subclasses will test as correct, but that's identical to how any other class's subclasses will work, so that seems expected - classes inherit their static members from their superclass as well, which will include the
[Symbol.matcher]method. There is literally no way to avoid this without bypassing the class system entirely (some sort of registry?) which seems bad. (Subclassing is, itself, often bad, but that's something every coder needs to learn for themselves, apparently.)ljharb commentedon Jul 11, 2022
Agreed. I'll prepare a PR to make that change.
Jack-Works commentedon Jul 12, 2022
if we can support this, it will be good.
5 remaining items
treybrisbane commentedon Jul 13, 2022
FWIW, as a rando developer, I feel that error type matching is one of the primary use cases for pattern matching.
As an example, I frequently write code at the API layer that converts domain errors to JSON responses, and that code is almost always something like "if the error is of this type, return this response, else if it's that type, return that response, ...". Pattern matching would be a big improvement for me here so long as I can easily differentiate different error types.
If the above example didn't work as-is in the final version of pattern matching, I'd personally be very, very sad.
Jack-Works commentedon Jul 13, 2022
I absolutely agree. Subclassing error is a widespread use case, it is worth the complexity of supporting it.
[spec] add MatchConstructorInstance AO to automatically handle subcla…
ljharb commentedon Jul 13, 2022
I've put up #279, on top of #273, which combined use instanceof semantics whenever the original constructor was not a builtin. I strongly dislike instanceof semantics, but I can't think of a more appropriate choice, sadly.
[spec] add MatchConstructorInstance AO to automatically handle subcla…
theScottyJam commentedon Jul 14, 2022
And, to be clear, is that change going to just make subclasses of Error work, or will it allow subclasses of all built-in types? (From my reading, it looks like the former?)
I don't feel overly strong about other built-in types, since I know they're rarely subclasses and really shouldn't be, except that it would be nice for consistency sake to make them all behave the same when subclassed.
Also, I know that, for example, HTMLDivElement is a browser API and not part of the core of EcmaScript, but I suspect browser APIs will follow whatever pattern we set here with their built-in types. And, I know they actually encorage subclassing HTMLDivElement (and all other HTML element classes), as that's the only way to make custom elements. It would be nice if pattern-matching against one of those userland subclasses just worked as well.
ljharb commentedon Jul 14, 2022
@theScottyJam #273 only makes builtin error subclasses work. #279 would make userland subclasses of any builtin constructor (except the primitives) work.
bathos commentedon Jul 14, 2022
Yep, Web IDL (at internally create a new object implementing interface) deliberately replicates the same observable behaviors as OrdinaryCreateFromConstructor, just using different spec terminology (because they don’t share the [[Intrinsics]] & %Name% syntax pattern). It seems likely that the
[[NewTarget]]slot logic could/would end up incorporated similarly because it was expressly stated in the past that alignment was the intention.HTML uses a special construct-algorithm override in place of the usual Web IDL steps via the [HTMLConstructor] extended attribute but delegates to the same at step 7.1. Because these constructors-when-used-as-super already have unique handling, there might be a good opportunity there to address the custom element scenario in particular with “automatic” subclass matching (esp. given the agent has privileged knowledge of whether instances are valid; there is effectively a kind of built-in branding for these subclasses). The only other commonly subclassed platform interface constructor I can think of is Event, but I don’t personally think implementing branded matchers is a big burden for that scenario — there’s already a need for special care along those lines when doing subclassing it.
ljharb commentedon Jul 14, 2022
@bathos i got some feedback that the slot approach won't likely be palatable to implementations, so i'm working on an alternative approach that walks the prototype, supports intrinsics from any realm, and gets
.constructorto do so. Both approaches should theoretically Just Work with web subclasses as well - HTML would only have to do extra work if it wanted things to work cross-realm for web builtins.[spec] add MatchConstructorInstance AO to automatically handle subcla…
[spec] add MatchConstructorInstance AO to automatically handle subcla…
tabatkins commentedon Jul 15, 2022
Yup, the #279 approach will automatically work for WebIDL objects, in fact, and userland subclasses of HTMLElement, since it proto-walks - so long as you don't want to do anything fancy,
when (${MyNewElement}): ...will Just Work.It doesn't even require any new special-cases unless we specifically want cross-window versions of WebIDL-defined classes to also match (which we might, I dunno, but if we do it's a pretty easy tweak to WebIDL to duplicate what #279 is doing for all WebIDL-defined classes as well).
[spec] add MatchConstructorInstance AO to automatically handle subcla…
[spec] add MatchConstructorInstance AO to automatically handle subcla…