{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/12939","repository_url":"https://api.github.com/repos/dotnet/roslyn","labels_url":"https://api.github.com/repos/dotnet/roslyn/issues/12939/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/roslyn/issues/12939/comments","events_url":"https://api.github.com/repos/dotnet/roslyn/issues/12939/events","html_url":"https://github.com/dotnet/roslyn/issues/12939","id":169488120,"number":12939,"title":"C# Design Notes for Jul 15, 2016","user":{"login":"MadsTorgersen","id":6677272,"avatar_url":"https://avatars.githubusercontent.com/u/6677272?v=3","gravatar_id":"","url":"https://api.github.com/users/MadsTorgersen","html_url":"https://github.com/MadsTorgersen","followers_url":"https://api.github.com/users/MadsTorgersen/followers","following_url":"https://api.github.com/users/MadsTorgersen/following{/other_user}","gists_url":"https://api.github.com/users/MadsTorgersen/gists{/gist_id}","starred_url":"https://api.github.com/users/MadsTorgersen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MadsTorgersen/subscriptions","organizations_url":"https://api.github.com/users/MadsTorgersen/orgs","repos_url":"https://api.github.com/users/MadsTorgersen/repos","events_url":"https://api.github.com/users/MadsTorgersen/events{/privacy}","received_events_url":"https://api.github.com/users/MadsTorgersen/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Area-Language%20Design","name":"Area-Language Design","color":"5319e7"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Design%20Notes","name":"Design Notes","color":"fc2929"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Language-C%23","name":"Language-C#","color":"207de5"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/New%20Language%20Feature%20-%20Out%20Variable%20Declaration","name":"New Language Feature - Out Variable Declaration","color":"bfd4f2"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/New%20Language%20Feature%20-%20Pattern%20Matching","name":"New Language Feature - Pattern Matching","color":"bfd4f2"}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":93,"created_at":"2016-08-04T22:28:12Z","updated_at":"2016-10-24T22:03:28Z","closed_at":null,"body":"# C# Design Language Notes for Jul 15, 2016\r\n\r\n## Agenda\r\n\r\nIn this meeting we took a look at what the scope rules should be for variables introduced by patterns and out vars.\r\n\r\n# Scope of locals introduced in expressions\r\n\r\nSo far in C#, local variables have only been:\r\n\r\n1. Introduced by specific kinds of statements, and\r\n2. Scoped by specific kinds of statements\r\n\r\n`for`, `foreach` and `using` statements are all able to introduce locals, but at the same time also constitute their scope. Declaration statements can introduce local variables into their immediate surroundings, but those surroundings are prevented by grammar from being anything other than a block `{...}`. So for most statement forms, questions of scope are irrelevant.\r\n\r\nWell, not anymore! Expressions can now contain patterns and out arguments that introduce fresh variables. For any statement form that can contain expressions, we therefore need to decide how it relates to the scope of such variables.\r\n\r\n## Current design\r\n\r\nOur default approach has been fairly restrictive:\r\n* All such variables are scoped to the nearest enclosing statement (even if that is a declaration statement)\r\n* Variables introduced in the condition of an `if` statement aren't in scope in the `else` clause (to allow reuse of variable names in nested `else if`s)\r\n\r\nThis approach caters to the \"positive\" scenarios of `is` expressions with patterns and invocations of `Try...` style methods with out parameters:\r\n\r\n``` c#\r\nif (o is bool b) ...b...; // b is in scope\r\nelse if (o is byte b) ...b...; // fine because bool b is out of scope\r\n...; // no b's in scope here\r\n```\r\n\r\nIt doesn't handle unconditional uses of out vars, though:\r\n\r\n``` c#\r\nGetCoordinates(out var x, out var y);\r\n...; // x and y not in scope :-(\r\n```\r\n\r\nIt also fits poorly with the \"negative\" scenarios embodied by what is sometimes called the \"bouncer pattern\", where a method body starts out with a bunch of tests (of parameters etc.) and jumps out if the tests fail. At the end of the test you can write code at the highest level of indentation that can assume that all the tests succeeded:\r\n\r\n```\r\nvoid M(object o)\r\n{\r\n  if (o == null) throw new ArgumentNullException(nameof(o));\r\n  ...; // o is known not to be null\r\n}\r\n```\r\n\r\nHowever, the strict scope rules above make it intractable to extend the bouncer pattern to use patterns and out vars:\r\n\r\n``` c#\r\nvoid M(object o)\r\n{\r\n  if (!(o is int i)) throw new ArgumentException(\"Not an int\", nameof(o));\r\n  ...; // we know o is int, but i is out of scope :-(\r\n}\r\n```\r\n\r\n## Guard statements\r\n\r\nIn Swift, this scenario was found so important that it earned its own language feature, `guard`, that acts like an inverted `if`, except that a) variables introduced in the conditions are in scope after the `guard` statement, and b) there must be an `else` branch that leaves the enclosing scope. In C# it might look something like this:\r\n\r\n``` c#\r\nvoid M(object o)\r\n{\r\n  guard (o is int i) else throw new ArgumentException(\"Not an int\", nameof(o)); // else must leave scope\r\n  ...i...; // i is in scope because the guard statement is specially leaky\r\n}\r\n```\r\n\r\nA new statement form seems like a heavy price to pay. And a guard statement wouldn't deal with non-error bouncers that correct the problem instead of bailing out:\r\n\r\n``` c#\r\nvoid M(object o)\r\n{\r\n  if (!(o is int i)) i = 0;\r\n  ...; // would be nice if i was in scope and definitely assigned here\r\n}\r\n```\r\n\r\n(In the bouncer analogy I guess this is equivalent to the bouncer lending the non-conforming customer a tie instead of throwing them out for not wearing one).\r\n\r\n## Looser scope rules\r\n\r\nIt would seem better to address the scenarios and avoid a new statement form by adopting more relaxed scoping rules for these variables.\r\n\r\n*How* relaxed, though?\r\n\r\n**Option 1: Expression variables are only scoped by blocks**\r\n\r\nThis is as lenient as it gets. It would create some odd situations, though:\r\n\r\n``` c#\r\nfor (int i = foo(out int j);;); \r\n// j in scope but not i?\r\n```\r\n\r\nIt seems that these new variables should at least be scoped by the same statements as old ones:\r\n\r\n**Option 2: Expression variables are scoped by blocks, for, foreach and using statements, just like other locals:**\r\n\r\nThis seems more sane. However, it still leads to possibly confusing and rarely useful situations where a variable \"bleeds\" out many levels:\r\n\r\n``` c#\r\nif (...)\r\n  if (...)\r\n    if (o is int i) ...i...\r\n...; // i is in scope but almost certainly not definitely assigned \r\n```\r\n\r\nIt is unlikely that the inner `if` intended `i` to bleed out so aggressively, since it would almost certainly not be useful at the outer level, and would just pollute the scope.\r\n\r\nOne could say that this can easily be avoided by the guidance of using curly brace blocks in all branches and bodies, but it is unfortunate if that changes from being a style guideline to having semantic meaning.\r\n\r\n**Option 3: Expression variables are scoped by blocks, for, foreach and using statements, as well as all embedded statements:**\r\n\r\nWhat is meant by an embedded statement here, is one that is used as a nested statement in another statement - except inside a block. Thus the branches of an `if` statement, the bodies of `while`, `foreach`, etc. would all be considered embedded.\r\n\r\nThe consequence is that variables would always escape the condition of an `if`, but never its branches. It's as if you put curlies in all the places you were \"supposed to\".\r\n\r\n## Conclusion\r\n\r\nWhile a little subtle, we will adopt option 3. It strikes a good balance:\r\n\r\n* It enables key scenarios, including out vars for non-`Try` methods, as well as patterns and out vars in bouncer if-statements.\r\n* It doesn't lead to egregious and counter-intuitive multi-level \"spilling\".\r\n\r\nIt does mean that you will get more variables in scope than the current restrictive regime. This does not seem dangerous, because definite assignment analysis will prevent uninitialized use. However, it prevents the variable names from being reused, and leads to more names showing in completion lists. This seems like a reasonable tradeoff."},"comment":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/comments/255879558","html_url":"https://github.com/dotnet/roslyn/issues/12939#issuecomment-255879558","issue_url":"https://api.github.com/repos/dotnet/roslyn/issues/12939","id":255879558,"user":{"login":"Kaelum","id":1742782,"avatar_url":"https://avatars.githubusercontent.com/u/1742782?v=3","gravatar_id":"","url":"https://api.github.com/users/Kaelum","html_url":"https://github.com/Kaelum","followers_url":"https://api.github.com/users/Kaelum/followers","following_url":"https://api.github.com/users/Kaelum/following{/other_user}","gists_url":"https://api.github.com/users/Kaelum/gists{/gist_id}","starred_url":"https://api.github.com/users/Kaelum/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Kaelum/subscriptions","organizations_url":"https://api.github.com/users/Kaelum/orgs","repos_url":"https://api.github.com/users/Kaelum/repos","events_url":"https://api.github.com/users/Kaelum/events{/privacy}","received_events_url":"https://api.github.com/users/Kaelum/received_events","type":"User","site_admin":false},"created_at":"2016-10-24T22:03:28Z","updated_at":"2016-10-24T22:03:28Z","body":"Well, for those who continue to use C#, more power to you.  I am extremely dismayed with the following statement by @MadsTorgersen:\r\n\r\n>However, for patterns you don't have that option. The pattern introduces the variable, and if you want that variable in the outer scope, there's nothing you can do. That's at the core of why we made the scopes the way we did.\r\n\r\nwhich completely ignores **Implicit Variable Declarations**  (#14697).  Thank you for proving my point, that you are not listening."}}
{"actor":{"display_login":"Kaelum"}}