Git Product home page Git Product logo

Comments (9)

MartinJohns avatar MartinJohns commented on September 27, 2024 2

Essentially a duplicate of #202, or at least requires that issue first.

from typescript.

rotu avatar rotu commented on September 27, 2024 2

Essentially a duplicate of #202, or at least requires that issue first.

Perhaps. That issue seems to be about user-defined types, whereas this is for platform-defined types.

Regardless, I think the machinery actually is in place to do limited nominal typing.

declare const PrimaryInterface : unique symbol

// https://webidl.spec.whatwg.org/#dfn-platform-object
type PlatformObject<in out idlInterface> = {
    /** @internal */
    readonly [PrimaryInterface] : idlInterface
}

type StructuralAbortSignal = AbortSignal
type NominalAbortSignal = PlatformObject<"AbortSignal">
type PlatformAbortSignal = NominalAbortSignal & StructuralAbortSignal

// this function will only check if signal is an "AbortSignal" - it won't suggest adding members to make it so
declare function myAbortable(signal: PlatformObject<"AbortSignal">):void;

myAbortable(null as any as PlatformAbortSignal) // okay

myAbortable(null as any as AbortSignal)
// Property '[PrimaryInterface]' is missing in type 'AbortSignal' but required in type 'PlatformObject<"AbortSignal">'.

myAbortable(null as any as {})
// Property '[PrimaryInterface]' is missing in type 'AbortSignal' but required in type 'PlatformObject<"AbortSignal">'.

myAbortable(null as any) // okay

Playground Link

from typescript.

rotu avatar rotu commented on September 27, 2024

Note that instanceof and such guards are not sufficient:

let s = Object.create(AbortSignal.prototype)
// s passes an instanceof check
console.assert(s instanceof AbortSignal)
// but s is still not admissable when expecting an AbortSignal
await fetch(
  'http://example.com',
  {signal: s}
) // runtime TypeError

from typescript.

jcalz avatar jcalz commented on September 27, 2024

Even if they went through the trouble of making AbortSignal a declared class with a phantom private member, the error messages would still be "misleading" up until someone tried adding the private member and got an error message about private members and declarations. And someone who went through the trouble of trying to build an object from the scads of missing members would just work around the private member issue somehow, like with a type assertion. TS's type system just isn't set up for nominal types. Normally I'd say "use X workaround" but here it really looks like you're asking for #202.

from typescript.

snarbies avatar snarbies commented on September 27, 2024

And someone who went through the trouble of trying to build an object from the scads of missing members would just work around the private member issue somehow, like with a type assertion.

I would hope and expect that even with "proper" nominal typing support, we would still be able to use type assertions when we believe we know better than the type checker. That's not really an argument against using nominal typing here, even if we have to roll our own.

from typescript.

jcalz avatar jcalz commented on September 27, 2024

This is all my opinion, so feel free to ignore it.

I'm not trying to argue against using nominal typing, I'm saying that without officially supported nominal typing, TS's error messages imply object that type mismatches are structural in nature. They all mention that such-and-such members are missing. Currently the private class member is the closest at giving you something that feels like a message about nominal types (I think if you manage to build your own class with all the same-named private members, you'll finally see a message that looks like "the private members are declared in different places"). Even your suggestion above with the unique symbol will give error messages about the fact that that property is missing (although it doesn't auto-suggest adding the property via IntelliSense).

Personally I think "you're missing a zillion members" is as likely to deter people as "you're missing this one magical member", and neither approach is going to stop the person we're imagining guarding against. Like, when you see an object type with a Date member, would it even occur to you to try to build one structurally? Start with {} and then debug your way to something TS accepts? Who is doing that for AbortSignal? For people like this, we'd really need #202.

from typescript.

rotu avatar rotu commented on September 27, 2024

The problem actually is the missing "one magical member" ([[PrimaryInterface]] internal slot). @jcalz, what error message would you have?

e.g. FireFox's runtime message is:

TypeError: Window.fetch: 'signal' member of RequestInit does not implement interface AbortSignal.

Chrome:

TypeError: Failed to execute 'fetch' on 'Window': Failed to read the 'signal' property from 'RequestInit': Failed to convert value to 'AbortSignal'.

Safari:

FetchRequestInit.signal should be undefined, null or an AbortSignal object. This will throw in a future release.

from typescript.

jcalz avatar jcalz commented on September 27, 2024

The problem at runtime is the internal slot, which isn't the same as a TS type with a symbol-valued key, and someone calling your code with myAbortable({ [PrimaryInterface]: "AbortSignal" }) will pass the TS check and still fail at runtime. I think we all agree you can currently simulate nominal typing by adding a random structural thing somewhere, but presumably native nominal typing would give the error message like "you can't assign {⋯} to AbortSignal. A value of type AbortSignal can only come from the same declaration site as AbortSignal. You cannot assemble one from an object literal." But that's all in #202.

Anyway, I think I might be repeating myself at this point and I'm not a TS team member so my opinion isn't going to move this issue in any direction. I'll bow out for now.

from typescript.

rotu avatar rotu commented on September 27, 2024

Yes, it's a hack. It's less of a hack than the current behavior: relying on the structural type of an AbortSignal as a proxy for what is actually expected (a [[PrimaryInterface]] internal slot). Indeed, an API expecting a platform object could have no structure from a javascript object perspective:

e.g.

const justASignal = Object.setPrototypeOf(AbortSignal.timeout(0), null)
fetch('http://example.com', {signal:justASignal})

I guess you could say that this is structural - it's just that the identifier is not exposed to the JavaScript runtime, and a symbol-typed key, which you're not going to dereference by accident, seems the closest equivalent in TypeScript today.

There are other internal slots (e.g. [ArrayBufferData], [PromiseState]) and extended attributes (e.g. [Serializable], [Transferable]) which would be handy to have from a type system perspective.

from typescript.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.