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

Allow stricter structural typing #391

Closed
jbrantly opened this issue Aug 7, 2014 · 7 comments
Closed

Allow stricter structural typing #391

jbrantly opened this issue Aug 7, 2014 · 7 comments
Labels
Breaking Change Would introduce errors in existing code Committed The team has roadmapped this issue Suggestion An idea for TypeScript

Comments

@jbrantly
Copy link

jbrantly commented Aug 7, 2014

Say I have the following:

interface Options {
    doSomethingSpecial?: boolean
}

function configure(options: Options) {}
configure({});

The preceding (correctly) does not flag any issues. However, say I change the function call to:

configure({doSomethingSuper: true});

This also does not flag any issues. I understand that in many (most?) cases this is the desired behavior, but in other cases it seems that a stricter matching (ie, you don't have to specify "doSomethingSpecial" but you can't specify anything else either) would be desirable. There are at least two instances that I can think of where this applies:

  1. Catching typos
  2. (more importantly) Compile-time checking after a refactoring of "doSomethingSpecial" to be named something else

Perhaps some new syntax for enabling this mode would be useful. For example:

function configure(options: Options!) {}
@danquirk
Copy link
Member

danquirk commented Aug 7, 2014

We've definitely had this sort of request before, doesn't appear to have been logged on GitHub yet.

The basic idea is to be able to specify a type that says 'you can/must have compatible properties x,y,z but you cannot have any additional properties' where currently TypeScript only allows a contract like 'you can/must have compatible properties x,y,z but you can have anything else too.' This is particularly problematic for options bags. Previous proposals have been along the lines of what you wrote:

interface MyOptions {
    isAwesome: boolean;
}
function doWork(options: sealed MyOptions) {}
doWork({ someUnknownProperty: 1 }); // error

Another alternative is that the type definition itself would be the place you specify this:

interface sealed MyOptions {
    theOnlyAllowedProperty: string;
}

but then you couldn't use this feature with object type literals, the type could only ever be used in a sealed fashion (maybe this is desirable), and you'd likely have to ensure sealed-ness was always matching between interface definitions. There're also questions of would your sealed-ness be inherited (I think not) and is it applied recursively to the types of your properties (doubtful).

Would be interested to hear whether people prefer it on type declarations, in type position (or both), and why. Are there are common patterns you'd use this for besides options bags?

Note: sealed is just one possible keyword, there're others and depending on the proposal sealed could actually be a bad choice due to the baggage it carries from other languages.

@jbrantly
Copy link
Author

jbrantly commented Aug 7, 2014

To answer your question about common patterns, I think wrappers around interfaces (eg http://backbonejs.org/#Model-set) would benefit from this. You have a well defined interface for your model and anything outside of that interface is likely wrong.

My preference would be allowing it on type declarations and in type position, but if I had to pick only one I would say in type position.

@NoelAbrahams
Copy link

I agree that this is a common source of errors - especially when refactoring code which results in properties being renamed.

Would be interested to hear whether people prefer it on type declarations, in type position (or both), and why

My preference is for the sealed keyword to go on the type declaration. This fits in with our code-base where we have a single clearly defined object literal (say a "Point" type with x and y properties) that is used in multiple places. Permitting sealed in a type position seems to complicate matters a bit too much, IMO.

See also the discussion "Should object literals with surplus properties be assignable to subtypes?" on the old codeplex site. I don't quite agree that this important piece of functionality should be left to a third party linting tool, as suggested in that discussion.

@mpawelski
Copy link

👍
definitely good and very useful feature suggestion. I had situations where this would find errors at compile time and I believe this what we strive for.

my opinion is the same as jbrantly:

My preference would be allowing it on type declarations and in type position, but if I had to pick only one I would say in type position.

@s-panferov
Copy link

Another example is ReactJS's props objects:

interface ChatCardComponentProps {
    isActive?: boolean;
}

render() {
    // first argument must be of ChatCardComponentProps type
    ChatCardComponent({
        isActive: /* some condition */ true
    })
}

Later component's author can rename interface property from isActive to isSelected and there will be an issue. We can't catch it by compiler now. This is very common case in practice.

👍 to the feature.

ps. I think that interface definition is a wrong place for keyword because this issue is about usage of type in some known context. Type position like function doWork(options: sealed MyOptions) {} is a good place for this.

Some ideas about naming: exact, strict or ! after the type function doWork(options: MyOptions!)

@kevinbarabash
Copy link

One thing to consider about moving the exact/strict specifier out of the type definition is that if you want the compiler to do the checking then, you'll have to add that specifier everywhere. example:

interface Options {
    altKey?: boolean;
    shiftKey?: boolean;
    ctrlKey?: boolean;
    metaKey?: boolean;
}

function simulate(element: HTMLElement, type: string, options: Options!) {
   // simulate the event
}

var options: Options!;
options = { shiftKey: false, maltKey: false };  // error

var moreOptions: Options;
options = { shiftKey: false, maltKey: false };  // okay

function simulateMouseDown(element: HTMLElement, options: Options) {
   simulate(element, "mousedown", options);  // ???
}

If we declare any parameter as exact/strict should you be able to pass it a variables of the same type that haven't been declared exact/strict? Same question with variable assignment.

I think the goal is avoid spelling mistakes when creating object literals. Already you can't add additional properties to objects of a specific interface if that interface doesn't already have it. Since object literals are the thing that's affected by the proposed change, maybe the new syntax should apply to the literal itself, e.g.

var options: Options;
options = {! shiftKey: false, maltKey: false };  // error

simulateMouseDown(document.body, {! shiftKey: false, maltKey: false }); // error

@ahejlsberg
Copy link
Member

Fixed in #3823.

@RyanCavanaugh RyanCavanaugh added Committed The team has roadmapped this issue and removed Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Jul 21, 2015
@mhegazy mhegazy added the Breaking Change Would introduce errors in existing code label Jul 21, 2015
@mhegazy mhegazy added this to the TypeScript 1.6 milestone Jul 21, 2015
@microsoft microsoft locked and limited conversation to collaborators Jun 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Breaking Change Would introduce errors in existing code Committed The team has roadmapped this issue Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

9 participants