{"action":"created","issue":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/1003","labels_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/1003/labels{/name}","comments_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/1003/comments","events_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/1003/events","html_url":"https://github.com/Microsoft/TypeScript/issues/1003","id":47291043,"number":1003,"title":"Singleton types under the form of string literal types","user":{"login":"Nevor","id":129594,"avatar_url":"https://avatars.githubusercontent.com/u/129594?v=3","gravatar_id":"","url":"https://api.github.com/users/Nevor","html_url":"https://github.com/Nevor","followers_url":"https://api.github.com/users/Nevor/followers","following_url":"https://api.github.com/users/Nevor/following{/other_user}","gists_url":"https://api.github.com/users/Nevor/gists{/gist_id}","starred_url":"https://api.github.com/users/Nevor/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Nevor/subscriptions","organizations_url":"https://api.github.com/users/Nevor/orgs","repos_url":"https://api.github.com/users/Nevor/repos","events_url":"https://api.github.com/users/Nevor/events{/privacy}","received_events_url":"https://api.github.com/users/Nevor/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/Microsoft/TypeScript/labels/Accepting%20PRs","name":"Accepting PRs","color":"009800"},{"url":"https://api.github.com/repos/Microsoft/TypeScript/labels/Suggestion","name":"Suggestion","color":"006b75"}],"state":"open","locked":false,"assignee":null,"milestone":{"url":"https://api.github.com/repos/Microsoft/TypeScript/milestones/2","html_url":"https://github.com/Microsoft/TypeScript/milestones/Community","labels_url":"https://api.github.com/repos/Microsoft/TypeScript/milestones/2/labels","id":721044,"number":2,"title":"Community","description":"Issues in the 'Community' milestone are ones which we are accepting pull requests for","creator":{"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},"open_issues":89,"closed_issues":35,"state":"open","created_at":"2014-07-14T23:24:58Z","updated_at":"2015-06-18T17:48:07Z","due_on":null,"closed_at":null},"comments":53,"created_at":"2014-10-30T16:21:54Z","updated_at":"2015-06-18T21:43:55Z","closed_at":null,"body":"This proposal is based on a working prototype located at https://github.com/Nevor/TypeScript/tree/SingletonTypes\r\n\r\n## String literal types extended to the whole language\r\n\r\nThis change would bring singleton types created from literal string to complete recent addition of type aliases and union types.  This would hopefully satisfy those that mentioned string enum and tagged union in previous PRs (#805, #186).\r\n\r\nThis addition would be short thanks to a concept that was already implemented internally for \".d.ts\" files.\r\n\r\n## Use cases \r\n\r\nOften operational distinction of function and values are done with an companion finite set of string that are used as tags. For instance events handler types rely on strings like \"mouseover\" or \"mouseenter\". \r\n\r\nSometimes string are often themselves operating on a finite definite set of values that we want to convey this through specifications and have something to enforce it. \r\n\r\nAnd for more advanced typing, we sometimes use types themselves as informations to guide function usages, constraining a little further the base types.\r\n\r\n## Current workarounds\r\nThere is no way to create string enum for now, the workaround is to manipulate variables assigned once and for all. This does not protect from typos that will gladly propagate everywhere a string is used.\r\n\r\nWhen we want to implement tagged union types for symbolic computation, we must use some number enum coupled with subtyping and casting machinery, losing all type safety.\r\n\r\nIn general, advanced type constraint are done through classes and this put us further away from simple records that would have been used in javascript.\r\n\r\n## Overview examples\r\n```typescript\r\ntype result = \"ok\" | \"fail\" | \"abort\";\r\n\r\n// \"string enum\"\r\nfunction compute(n : number) : result {\r\n  if(...) {\r\n    return \"ok\";\r\n  } else if (...) {\r\n    return \"fail\";\r\n } else {\r\n    return \"crash\"; // Error, not accepted\r\n }\r\n}\r\n\r\nfunction checkSuccess(o : result) : void { ... }\r\n\r\nvar res : result = compute(42);\r\n\r\ncheckSuccess(res); // OK\r\ncheckSuccess(\"crash\"); // Error\r\n\r\nres = \"crash\"; // Error\r\n\r\nvar message = res; // OK, message infered as string\r\nvar verbose = \"Current status : \" + res; // OK\r\n\r\n\r\n// Specifications constrains\r\ninterface operationAction {\r\n  name : string;\r\n  id : number;\r\n  status : result;\r\n}\r\n\r\n// Usable as and with regular types\r\nvar results : result[] = [compute(3), compute(27), \"ok\"]; // Ok\r\nresults = [\"crash\", \"unknown\"]; // Error\r\n\r\ntype error_level = \"warning\" | \"fatal\";\r\n\r\ninterface Foo<T> {\r\n  foo : T;\r\n}\r\ninterface Toto<U> {\r\n  value : Foo<result> | { unknown : string; value : U };\r\n}\r\n\r\nvar foo : Toto<error_level> = { foo : \"ok\" }; // OK\r\nvar foo : Toto<error_level> = { foo : \"crash\" }; // Error\r\nvar foo : Toto<error_level> = { unknown : \"Unknown error\", value : \"warning\" }; // OK\r\nvar foo : Toto<error_level> = { unknown : \"Unknown error\", value : \"trace\" }; // Error\r\n\r\n\r\n// Disjoint union\r\ntype obj = { kind : \"name\"; name : string } | { kind : \"id\"; id : number } | { kind : \"internal\" ; id : number }\r\n\r\nvar o : obj = { kind : \"name\", name : \"foo\", id : 3 } \r\nvar o : obj = { kind : \"id\", name : \"foo\", id : 3 } \r\n// Both object are strictly distinguished by their kind\r\n\r\nvar o : obj = { kind : \"unknown\", name : \"something\" }; // Error\r\n\r\ntype classical_obj : { name : string } | { id : number };\r\n\r\nvar c_o : classical_obj = { name : \"foo\", id : 3 }; // A supertype of both is assignable, we lose infos \r\n\r\n```\r\n## Typing specifications\r\n- any string literal as type \"s\" (StringLiteralType) where s is the content of the literal.\r\n- every StringLiteralTypes are subtypes of string, the reverse is not true.\r\n- two StringLiteralTypes are compatible if and only if they represent the same string literal.\r\n\r\n```typescript\r\ntype ok = \"ok\"\r\n\r\n\r\nvar a : ok = \"ok\";\r\nvar b : \"ok\" = a; // OK\r\n\r\nvar a  : ok = \"no\"; // Error\r\nvar c : string = a; // OK \r\nvar a : ok = c; // Error \r\n```\r\n\r\n## Pitfalls and remaining work\r\n\r\n### Backward compatibility\r\nThis prototype is backward compatible (accepts programs that were accepted before) but in one case : \r\n```typescript\r\nfunction ... {\r\n  if (...) { \r\n    return \"foo\";\r\n } else {\r\n    return \"bar\";\r\n }\r\n}\r\n```\r\nThe compiler will raise an Error saying that there is no common type between \"foo\" and \"bar\". This is because the compiler only accept one of the return type to be supertype and does not widen before.\r\nWe might add a special case for StringLiteralTypes and keep other types as is, or, do some widening and therefore accept empty records for conflicting records for instance.\r\n\r\n### Error messages\r\nIt might confuse users that their literal strings are mentioned as types when they are expecting to see \"string\" even though this difference as no incidence on normally rejected string.\r\nThe compiler might display StringLiteralTypes as \"string\" whenever the conflict is not involved between two StringLiteralTypes.\r\n\r\n## Extending type guards \r\nTo be fully usable to distinguish records by type tags, type guards should be extended to take into account this kind singleton types. One would expect the following to work : \r\n\r\n```typescript\r\ntype obj = { kind : \"name\"; name : string } | { kind : \"id\" ; id : number };\r\nvar o : obj = ...;\r\nif (o.kind == \"name\") {\r\n  /* o considered as left type */\r\n} else if(o.kind == \"id\")  {\r\n  /* o considered as right type */\r\n}\r\n```"},"comment":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/comments/113297932","html_url":"https://github.com/Microsoft/TypeScript/issues/1003#issuecomment-113297932","issue_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/1003","id":113297932,"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-06-18T21:43:55Z","updated_at":"2015-06-18T21:43:55Z","body":"Talked with @mhegazy. We think that this issue really doesn't have much to do with string literal types. Instead it has more to do with type guards that involve property accesses. To this end, our inclination is that if you have a type guard based on a property access, we will only narrow the type in the true branch. The false branch could trick us into doing a faulty process of elimination."}}