{"action":"created","issue":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/3755","labels_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/3755/labels{/name}","comments_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/3755/comments","events_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/3755/events","html_url":"https://github.com/Microsoft/TypeScript/issues/3755","id":93382098,"number":3755,"title":"Strict Interfaces","user":{"login":"RyanCavanaugh","id":6685088,"avatar_url":"https://avatars.githubusercontent.com/u/6685088?v=3","gravatar_id":"","url":"https://api.github.com/users/RyanCavanaugh","html_url":"https://github.com/RyanCavanaugh","followers_url":"https://api.github.com/users/RyanCavanaugh/followers","following_url":"https://api.github.com/users/RyanCavanaugh/following{/other_user}","gists_url":"https://api.github.com/users/RyanCavanaugh/gists{/gist_id}","starred_url":"https://api.github.com/users/RyanCavanaugh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/RyanCavanaugh/subscriptions","organizations_url":"https://api.github.com/users/RyanCavanaugh/orgs","repos_url":"https://api.github.com/users/RyanCavanaugh/repos","events_url":"https://api.github.com/users/RyanCavanaugh/events{/privacy}","received_events_url":"https://api.github.com/users/RyanCavanaugh/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/Microsoft/TypeScript/labels/In%20Discussion","name":"In Discussion","color":"556b75"},{"url":"https://api.github.com/repos/Microsoft/TypeScript/labels/Suggestion","name":"Suggestion","color":"006b75"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":4,"created_at":"2015-07-06T21:33:54Z","updated_at":"2015-07-07T21:33:36Z","closed_at":null,"body":"## Motivating Examples\r\n\r\nToday, TypeScript does not correctly detect certain kinds of errors:\r\n```ts\r\ninterface TextOptions {\r\n\talignment?: string;\r\n\tcolor?: string;\r\n\tpadding?: number;\r\n}\r\nfunction drawText(opts: TextOptions) { ... }\r\n\r\n// No error\r\ndrawText({ align: 'center'} );\r\n\r\n// No error\r\nfunction getDefaultOptions: TextOptions {\r\n\treturn { colour: 'blue'; };\r\n}\r\n\r\n// No error, wat?!\r\ndrawText(getDefaultOptions);\r\n\r\n// Still no error\r\ndrawText(getDefaultOptions());\r\n\r\n// Give me a break\r\ndrawText(42);\r\n```\r\n\r\nThere are two core problems here:\r\n 1. Interfaces with all optional properties are assignable *from* anything with a disjoint set of properties (a set which includes the vast majority of values)\r\n 2. There is no way to say in the type system \"These are the only things I can handle\", e.g. options bags should *not* be open-ended from an assignability/subtyping perspective\r\n \r\nAdditionally, there is no good workaround for this problem.\r\n\r\nThis is a common problem to encounter; see #1327, #391, #1817, #3489, many emails we've received internally, http://stackoverflow.com/questions/29207118/typescript-adding-only-an-optional-property-removes-type-checking, http://stackoverflow.com/questions/13213739/why-are-undeclared-object-properties-not-flagged-as-errors, http://stackoverflow.com/questions/31221370/why-does-typescript-not-check-the-naming-for-the-contents-of-an-array-of-objects\r\n\r\n## Strict Interfaces\r\n\r\nA `strict` interface is a type `S` that is *not* a supertype of any other type `T` which has properties not present in `S`.\r\n\r\n### Syntax\r\n\r\nAn `interface` declaration may include the `strict` modifier:\r\n```ts\r\nstrict interface TextOptions {\r\n\talignment?: string;\r\n\tcolor?: string;\r\n\tpadding?: number;\r\n}\r\n```\r\n\r\nWhen this modifier is present, types with surplus properties are not assignable to or subtypes of that interface:\r\n\r\n```ts\r\n// Error\r\ndrawText({ align: 'center'} );\r\n\r\n// Error\r\nfunction getDefaultOptions: TextOptions {\r\n\treturn { colour: 'blue'; };\r\n}\r\n\r\n// Error\r\ndrawText(getDefaultOptions);\r\n\r\n// Error\r\ndrawText(getDefaultOptions());\r\n```\r\n\r\n### Uses\r\n`strict` interfaces are important to address two common scenarios:\r\n\r\n * Defining property bags, where an unknown option name is always going to be disregarded\r\n * Defining all-optional interfaces that still retain safety against property name typos\r\n\r\n## Decision Points\r\nThere are a few pivot points. Let's review each.\r\n\r\n> Can I extend a `strict` interface? Isn't `strict` the same as `sealed`?\r\n\r\nIt is valid to `extend` a `strict` interface. `strict` **is not** `sealed` or `final`! Extending a strict interface is an important ability; for example if you wrap some other API and want to re-use their options bag plus some of your own, extending the interface to add more members makes a lot of sense.\r\n\r\n> Can I augment a `strict` interface?\r\n\r\nYes. Again, adding members to an options bag is important for modeling plug-ins.\r\n\r\n> Is an interface extended from a `strict` interface also `strict` ?\r\n\r\nNo. A subtype of a strict interface may or may not want to be strict; by defaulting to 'off' the choice can be made more naturally (rather than trying to add syntax to turn off strictness on a certain declaration).\r\n\r\n> Can I `implements` a `strict` interface?\r\n\r\nYou may, but a class will almost certainly have more properties than the strict interface it implements. We might consider disallowing this because it's so unlikely to actually work in practice.\r\n\r\n> Should `strict`ness be required to be present on the source type?\r\n\r\nThis is a tempting proposal. Consider this code:\r\n```ts\r\nstrict interface Foo { a: number }\r\nlet x = { a: 10, bb: 3 };\r\nlet y: { a: number } = x;\r\nlet z: Foo = y; // z has a 'bb' property now\r\n```\r\n\r\nIt is undesirable for `z` to have a `bb` property as a result of the aliased use of `x`. A proposed solution is to contextually type object literals with strictness (similar to how we treat index signatures) and require strictness in the source type if the target type is strict. However, this would break some very common JavaScript patterns:\r\n\r\n```ts\r\nlet opts = { url: 'example.com' };\r\ndoSomething(opts); // error because opts isn't 'strict'?\r\n```\r\n\r\nIdeally, we would cause the first example to be an error and the second example to *not* be an error, but it's unclear how to do that. @RyanCavanaugh wants them to both be non-errors; @JsonFreeman wants them to both be errors.\r\n\r\n> Is `strict` applied at declaration, or use?\r\n\r\nDiscussion in #391 looked at whether this modifier should be part of a type declaration (`strict interface S { ... }`, a type reference (`function fn(x: !S) { ... }`, or an object literal (`var x = {! ... }`). As usual, we prefer not to expand expression syntax. Making strictness part of the type reference is an OK option, but most types that 'want' to be strict should be strict at all use sites, so it's better to make this part of the declaration so that consumption of it is less error-prone.\r\n\r\n## Spec Details\r\n\r\nA few notes on how this impacts the spec:\r\n \r\n * Do not take the apparent type of the source type when the target type is strict. Adding the apparent members from `Object` will cause the relation to fail.\r\n * When checking `interface A extends B { ... }` where `B` is strict, `B`'s strictness is ignored for the check that `A` is assignable to `B` (otherwise `A` could declare no new members!)\r\n \r\n ## Implementation\r\n\r\nhttps://github.com/Microsoft/TypeScript/compare/master...RyanCavanaugh:stricterfaces"},"comment":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/comments/119349006","html_url":"https://github.com/Microsoft/TypeScript/issues/3755#issuecomment-119349006","issue_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/3755","id":119349006,"user":{"login":"JsonFreeman","id":8052525,"avatar_url":"https://avatars.githubusercontent.com/u/8052525?v=3","gravatar_id":"","url":"https://api.github.com/users/JsonFreeman","html_url":"https://github.com/JsonFreeman","followers_url":"https://api.github.com/users/JsonFreeman/followers","following_url":"https://api.github.com/users/JsonFreeman/following{/other_user}","gists_url":"https://api.github.com/users/JsonFreeman/gists{/gist_id}","starred_url":"https://api.github.com/users/JsonFreeman/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JsonFreeman/subscriptions","organizations_url":"https://api.github.com/users/JsonFreeman/orgs","repos_url":"https://api.github.com/users/JsonFreeman/repos","events_url":"https://api.github.com/users/JsonFreeman/events{/privacy}","received_events_url":"https://api.github.com/users/JsonFreeman/received_events","type":"User","site_admin":false},"created_at":"2015-07-07T21:33:36Z","updated_at":"2015-07-07T21:33:36Z","body":"In particular, I have discussed with @RyanCavanaugh and it seems that strict interfaces have a really weird interaction with intersection types."}}