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

Stricter interfaces/literal types #6352

Closed
ewinslow opened this issue Jan 5, 2016 · 7 comments
Closed

Stricter interfaces/literal types #6352

ewinslow opened this issue Jan 5, 2016 · 7 comments
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript

Comments

@ewinslow
Copy link

ewinslow commented Jan 5, 2016

I love #3823 and the way it catches lots of errors early. Unfortunately, it doesn't quite go as far as I'd like it to. Suppose you have code like this, where inlining all properties of styles.header is undesirable for whatever reason (e.g., you can imagine such a block getting very large with many style options to set):

function applyStyles(el: Element, style: {backgroundColor: string}) {}

const styles = {
  header: {
    backroundColor: '#eee',
  },
};

applyStyles(document.getElementById('#header'), styles.header);

Here, the applyStyles call will not catch the extra "typo" property (backroundColor instead of backgroundColor) in the shape of styles.header, which has all optional properties (corresponding to Element.prototype.style properties).

My only option to make sure these things checked right now is I guess to maintain a whitelist of accepted properties that is verified at runtime for each call to the function in question. A way to check this at compile time instead seems like it would be pretty valuable. I know this was discussed before and turned down in favor of only checking inline object literals, but I didn't see this kind of use-case clearly articulated in that discussion so I figured I'd open another issue and see if anyone else still thinks there should be a way to say "no, truly, do not ever pass me anything other than this set of options".

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Jan 5, 2016
@RyanCavanaugh
Copy link
Member

Obviously we'd need a description of some desired semantics to make a call, but I think the bar here is relatively high. The type of style is going to be some named interface if it's of any reasonable size, at which point you could write

interface SupportedStyles { backgroundColor?: string }
function applyStyles(el: Element, style: SupportedStyles) {}
const styles: { [key: string]: SupportedStyles } = {
  header: {
    backroundColor: '#eee', // Error issued here
  },
};

@ewinslow
Copy link
Author

ewinslow commented Jan 5, 2016

That seems like a reasonable workaround for now. Thanks. I'll come back with any more relevant info after I try that.

@ewinslow
Copy link
Author

@RyanCavanaugh So I tried out your suggestions, but I get an error when I try to access styles.header

interface StyleProps {backgroundColor: string;}
function applyStyles(el: Element, style: StyleProps) {}

const styles: {[key:string]: StyleProps} = {
  header: {
    backroundColor: '#eee',
  },
};

// Error: unknown property "header" on {[key:string]:StyleProps}
applyStyles(document.getElementById('#header'), styles.header);

If I switch to styles['header'] it works, but that isn't quite what I want either.

@RyanCavanaugh
Copy link
Member

You could do this? I'm not sure I'm handling all your use cases here, though:

interface StyleProps {backgroundColor: string;}
function applyStyles(el: Element, style: StyleProps) {}

const styles = {
  header: {
    backgroundColor: '#eee',
  } as StyleProps
};

// OK
applyStyles(document.getElementById('#header'), styles.header);

@ewinslow
Copy link
Author

Circling back: {foo: 'bar'} as StyleProps passes typechecking, since I think the as StyleProps is just a cast, right? It's effectively the same as {header: <StyleProps>{foo: 'bar'}}.

The workaround I'm thinking of at this point is to just have a styleProps function that takes StyleProps argument:

function styleProps(props: StyleProps) { return props; }

Then this will be type-checked:

const styles = {
header: styleProps({
backroundColor: '#000', // Error, unknown property in object literal
}),
};

It's just unfortunate that this kind of boilerplate is required since it relies on callers being well-behaved.

It doesn't matter too much in the StyleProps use-case since the issue of passing unknown properties is relatively low impact. However, this becomes more "interesting" in a security-sensitive context where I want to whitelist what properties can be assigned to a dom node:

interface DivProps {
  style?: StyleProps;
}
function div(props: DivProps): HTMLDivElement {}

div({onclick: 'alert("pwnd")'}); // Error. Yay! no XSS.

const props = {onclick: 'alert("pwnd")'};
div(props); // No error. XSS :(

That being said, I suppose the only truly safe way to defend against this kind of thing is runtime whitelists, since all the type-checking can just be trivially defeated by introducing any. It'd just be nice if more of these checks could happen automatically at compile time.

@laszlojakab
Copy link

laszlojakab commented May 11, 2016

Maybe the same problem related to union types:

interface X {

}

interface Y {
    z: Z;
}

interface Z {
    a: string;
    b: string;
}

let z: Z = { // Error, which is ok: "Type: '{}' is not assignable to type 'Z'. Property 'a' is missing in type '{}'."
};

let foo: X | Y = {
    z: { // Not an error but it should be: if foo is X then it shouldn't contain field 'z', if foo is Y then 'z' field should contain 'b'
        a: "abc"
    }
}

Can I force the compiler to show an error message if "b" field is not provided at "foo.z.b"?

@RyanCavanaugh
Copy link
Member

This is an upstream wish for exact types or the "non-casting type assertion" operator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants