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

Bad types of hasOwnProperty #20363

Closed
nenadalm opened this issue Nov 30, 2017 · 4 comments
Closed

Bad types of hasOwnProperty #20363

nenadalm opened this issue Nov 30, 2017 · 4 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@nenadalm
Copy link

Hi. hasOwnProperty has following type:

hasOwnProperty(v: string): boolean;

which is not that useful for type system.

I wanted to write more useful version, but it seems to be impossible to type. So far best I could come up with is:

function hasProp<P extends string, O extends object>(prop: P, object: O): object is (O & Record<P, u.AnyValue>) {
    return object.hasOwnProperty(prop);
}

function hasProp2<P extends string, O extends object>(prop: P, object: O): prop is keyof object {
    return object.hasOwnProperty(prop);
}

function propIsDefined<P extends string, O extends object>(prop: P, object: O) {
    if (!hasProp(prop, object)) {
        throw new Error();
    }

    if (!hasProp('wtf', object)) {
        throw new Error();
    }

    // following line shows error, even though the prop was checked before.
    const val = object[prop];

    // following line works - `val2` has type `u.AnyValue`
    // so the function `hasProp` only works if `prop` argument is passed as string (not string variable)
    const val2 = object.wtf;

    if (!hasProp2(prop, object)) {
        throw new Error();
    }

    // following line works, but type of `val3` is any, which is bad, because it means
    // that typescript won't provide any level of type safety
    // I wasn't able to type the function so that `val3` is of type `u.AnyType`
    const val3 = object[prop];
}

type u.AnyType mentioned above is defined like so:

// https://github.com/Microsoft/TypeScript/issues/6230#issuecomment-212755825
export interface AnyObject {
    [key: string]: AnyValue;
}
export interface AnyArray extends Array<AnyValue> {}
export type AnyValue =
    string |
    number |
    boolean |
    null |
    undefined |
    AnyArray |
    AnyObject;

purpose of it is that I can work safely with values for which type is not known (as any allows me to do whtatever I want without checking anything).

@jcalz
Copy link
Contributor

jcalz commented Nov 30, 2017

You can use global augmentation to do something like

declare global {
  interface Object {
    hasOwnProperty<K extends string>(v: K): this is Record<K, any>;
  }
}

where you can replace the any in Record<K, any> with {} | undefined | null or whatever top type you deem necessary.

And then it would work like:

function doStuff(x: object) {
  if (x.hasOwnProperty('b')) {
    x.b = 1; // okay
    if (x.hasOwnProperty('c')) {
      x.c = x.b; // okay
    }
  } 
}

which mostly gives you what you want, unless I'm missing something.

@nenadalm
Copy link
Author

@jcalz the thing is that I am not able to type the function so that it works. See the function propIsDefined.


If I call function hasProp like this:

hasProp('wtf', object);

it works as expected and I can assign the prop to the variable:

const val2 = object.wtf;

If I call the function with prop as string variable instead of string though:

hasProp(prop, object)

I cannot assign the prop to the variable because typescript shows some error

const val = object[prop];

As for me, the 2 examples are the same, but typescript understands only the with first one (extracted the code from my previous example to highlight the interesting lines).

@mhegazy mhegazy added the Question An issue which isn't directly actionable in code label Nov 30, 2017
@jcalz
Copy link
Contributor

jcalz commented Nov 30, 2017

So your problem is you want to narrow both the object and the key with a single type guard. (Narrowing the key is probably not going to happen in the standard library, but you can do it in your own module with global augmentation.) And that isn't currently possible. There's no good way right now to manipulate type predicates like object is Record<P, any> and prop is keyof O to form their intersection... the compiler just widens them to boolean instead of propagating them through.

I'm not sure if this issue is a duplicate of #12798 or #10734 with a little of #12892 thrown in, or if I've misunderstood.

@nenadalm
Copy link
Author

nenadalm commented Dec 1, 2017

@jcalz thanks. It seems to be exactly what #12892 is about. I am closing it as it's duplicate of it.

@nenadalm nenadalm closed this as completed Dec 1, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

3 participants