Compare commits

...

224 commits

Author SHA1 Message Date
Rikki Gibson 4df3296a0f
Update param-nullchecking proposal (#5397) 2021-11-10 15:17:49 -08:00
Mads Torgersen 9b15547365
Update LDM agenda 2021-11-10 07:15:16 -08:00
Charles Stoner 87b42f24ad
Update lambda-improvements.md to match language design decisions (#5388)
* Include anonymous methods for function types and conversions

* Remove Direct invocation section

* 'var' cannot be used as explicit return type

* No inferred type for discard

* Function type conversions are ignored for user-defined conversions

* Update better function member and better conversion from expression

* Warning converting method group to object

* Move open issues to dedicated section

* Function types are used in a few specific contexts

* Removed design meeting links (moved to championed issue)
2021-11-09 12:02:49 -08:00
Charles Stoner df1a9276ff
Fix relative links in params-span.md (#5389) 2021-11-08 11:01:59 -08:00
Bernd Baumanns 3369227509
Fix linq (#5390)
* missing curly brace
2021-11-08 10:57:20 -08:00
Charles Stoner 73077e88ca
Add proposal for params Span<T> (#5382) 2021-11-05 17:43:34 -07:00
AlekseyTs 28efe56136
Explicitly specify the order of evaluation for implicit Index/Range support (#5380)
This is a follow up on an LDM decision, see https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-11-01.md for more information.
2021-11-05 06:40:36 -07:00
CyrusNajmabadi 338b5908b6
Update LDM-2021-11-03.md 2021-11-03 17:22:19 -07:00
Fredric Silberberg e51570659f
Added LDM notes for November 3rd, 2021. 2021-11-03 17:12:26 -07:00
Nigel Bess 1f7a1340e1
Update LDM-2021-04-14.md (#5383)
Fixed typo
2021-11-03 10:12:12 -07:00
Fredric Silberberg b7f3a6653c
Corrected notes on Index and Range. 2021-11-02 09:44:28 -07:00
Fredric Silberberg d0176fe491
Correct ordering 2021-11-01 17:05:43 -07:00
Fredric Silberberg 3514aabe7c
Added LDM Notes for November 1st, 2021 2021-11-01 16:47:12 -07:00
Mads Torgersen 2246988f5d
Update LDM agenda 2021-11-01 10:28:22 -07:00
Charles Stoner 26ccd64e79
Update README.md 2021-10-31 00:04:48 -07:00
Fred Silberberg 43d9ed40c1
Update the interpolated string handler spec (#5365)
Updates the spec with the changes implemented in https://github.com/dotnet/roslyn/pull/57456, after discussion with LDM.
2021-10-29 21:02:04 -07:00
Alireza Habibi cb5871f620
Update list-patterns.md (#5364) 2021-10-29 13:44:15 -07:00
Charles Stoner 6a76c1cf68
Update README.md 2021-10-29 11:30:49 -07:00
Mads Torgersen 7bdf60f57a
Update LDM agenda 2021-10-28 17:54:44 -07:00
Charles Stoner 87137e67cc
Update README.md 2021-10-28 17:31:03 -07:00
Fredric Silberberg 81f2670ec5
Added LDM notes for September 27th, 2021 2021-10-27 17:50:30 -07:00
Mads Torgersen 2e10a1b56d
Update LDM agenda 2021-10-27 09:56:17 -07:00
Fred Silberberg 42b18c5d20
Fixup utf8 string proposal (#5355) 2021-10-27 09:50:16 -07:00
CyrusNajmabadi 0f09d0ffdd
Update README.md 2021-10-27 09:46:06 -07:00
Jared Parsons 5e25bf6e3a
Utf8 string literal proposal (#5349)
* Have to start somewhere

* Progress

* Progress

* Initial draft complete

* Apply suggestions from code review

Co-authored-by: Stephen Toub <stoub@microsoft.com>

* Suggestions

Co-authored-by: Stephen Toub <stoub@microsoft.com>
2021-10-27 08:49:24 -07:00
Mads Torgersen 8f13ded863
Update LDM agenda 2021-10-26 11:03:14 -07:00
Fredric Silberberg 2802e29f4c
Added LDM Notes for October 25th, 2021 2021-10-25 15:43:12 -07:00
Mads Torgersen ec01d26c47
Update LDM agenda 2021-10-22 14:50:59 -07:00
Mads Torgersen 2ba7e98c33
Add Nov and Dec dates to LDM agenda 2021-10-21 17:02:41 -07:00
Fredric Silberberg 9f54c9673f
Added LDM Notes for October 20th, 2021. 2021-10-20 16:27:56 -07:00
Julien Couvreur e8ddd37721
Remove value type restriction on nullable suppressions
A recent issue prompted me to double-check this: https://github.com/dotnet/roslyn/issues/57142
Original design decision: https://github.com/dotnet/roslyn/issues/29907
2021-10-19 16:57:48 -07:00
Mads Torgersen f20dd3076e
Update primary constructors proposal 2021-10-19 16:57:31 -07:00
Mads Torgersen 95ffad807f
Merge pull request #5306 from dotnet/madst
Create non-record-primary-constructors.md
2021-10-19 16:55:33 -07:00
Mads Torgersen ce40037cbf Update non-record-primary-constructors.md
React to @jaredpar comments about fields vs parameters
2021-10-19 16:47:39 -07:00
Mads Torgersen b6f2fb7598
Update proposals/non-record-primary-constructors.md
Co-authored-by: Jared Parsons <jaredpparsons@gmail.com>
2021-10-19 16:25:33 -07:00
Mads Torgersen 8265129d86 Create non-record-primary-constructors.md
Rewrite proposal from primary_constructors.md in light of the primary constructors already available on records.
2021-10-19 15:18:58 -07:00
Charles Stoner 90f617d0a7
Remove separate lambda-attributes.md proposal (#5301) 2021-10-18 18:11:39 -07:00
Mads Torgersen dd9c089e9e
Update LDM agenda 2021-10-15 13:33:54 -07:00
CyrusNajmabadi 762f5da1d2
Update semi-auto-properties.md 2021-10-15 12:53:13 -07:00
CyrusNajmabadi e4ffa88c9f
Update semi-auto-properties.md 2021-10-15 12:52:44 -07:00
CyrusNajmabadi b8fa5f0689
Update semi-auto-properties.md 2021-10-15 12:51:37 -07:00
CyrusNajmabadi d91984fe41
Update semi-auto-properties.md 2021-10-15 12:49:17 -07:00
CyrusNajmabadi cf102bcbb2
Create semi-auto-properties.md 2021-10-15 12:47:27 -07:00
CyrusNajmabadi a7aa8230e6
Update new-line-in-interpolation.md 2021-10-15 10:27:05 -07:00
CyrusNajmabadi f548a2fea8
Rename new-line-in-interpolation to new-line-in-interpolation.md 2021-10-14 14:05:11 -07:00
CyrusNajmabadi 0130349385
Create new-line-in-interpolation 2021-10-14 14:04:57 -07:00
Fredric Silberberg 7e2455955c
Added LDM notes for October 13th, 2021. 2021-10-13 15:34:01 -07:00
Mads Torgersen 6934787776
Update LDM agenda 2021-10-12 10:51:53 -07:00
Julien Couvreur 5675cc01cd
Update README.md 2021-10-08 16:55:36 -07:00
Charles Stoner a6737cff25
Several corrections to task-types.md (#5253) 2021-10-06 21:41:54 -07:00
Bill Wagner 1bbea06217
fix typos (#5250)
find / replace mistake, where I had // instead of /
2021-10-05 20:20:18 -04:00
AlekseyTs eedd69b8f9
Update "Unresolved questions" in static-abstracts-in-interfaces.md (#5249) 2021-10-05 14:49:13 -07:00
Bill Wagner f5daa3c480
Update links to spec (#5248)
Making the links to the spec relative links, so they'll resolve correctly both in this repo, and when published on docs.microsoft.com

(The latest change doesn't have the folder as part of it.)
2021-10-05 17:40:03 -04:00
Alireza Habibi 6e57bef953
Add pattern-variables.md (#4592) 2021-10-04 13:02:43 -07:00
Mads Torgersen 5a4d8a5903
Add LDM dates 2021-10-03 15:33:52 -07:00
Julien Couvreur a012a07b58
Update README.md 2021-10-01 14:42:07 -07:00
Fred Silberberg 0eb5952478
Fix the intro sections of the spec. (#5239) 2021-10-01 13:50:34 -07:00
Petr Onderka 41f962b156
Deleted confusing task lists from proposals (#5232) 2021-09-28 12:51:58 -07:00
Julien Couvreur 5c70a7acac
Update README.md 2021-09-28 10:56:55 -07:00
Julien Couvreur 40e8483671
Update README.md 2021-09-27 10:43:16 -07:00
CyrusNajmabadi 0ea5665946
Merge pull request #5174 from dotnet/CyrusNajmabadi-patch-1
Create using-alias-types.md
2021-09-24 17:50:00 -05:00
Charles Stoner d52e29f186
Lambda improvements: include 'Explicit return type inference' spec section (#5217) 2021-09-24 10:38:05 -07:00
Fred Silberberg c33a21734e
Fix example 2021-09-23 11:20:38 -07:00
Alireza Habibi 138765d6b0
Fix code example in notes (#5215) 2021-09-23 08:31:23 -07:00
Fredric Silberberg 38794f2c5b
Add missing notes link. 2021-09-22 17:39:46 -07:00
Fredric Silberberg 17d51b5875
Remove brackets 2021-09-22 17:37:42 -07:00
Fredric Silberberg 10f366592c
Added LDM notes fo rSeptember 22nd, 2021 2021-09-22 17:36:26 -07:00
Julien Couvreur 33e3bfe31f
Update README.md 2021-09-22 12:01:08 -07:00
Joseph Musser 088f20b6f9
Fix typos (#5210) 2021-09-21 09:53:30 -07:00
Charles Stoner fab06e432c
Correct formatting of generic type name in LDM notes (#5209) 2021-09-21 08:41:19 -07:00
Fredric Silberberg 4109c3d3de
Added LDM Notes for September 15th and 20th. 2021-09-20 18:21:14 -07:00
Mads Torgersen c1ccfd0d6a
Update LDM agenda 2021-09-19 09:54:00 -07:00
Charles Stoner 6f9ae4dccd
Update README.md 2021-09-17 15:52:13 -07:00
Mads Torgersen 5d5688cdac
Update LDM agenda 2021-09-17 15:45:48 -07:00
Mads Torgersen 794e7fa1d9
Update LDM agenda 2021-09-16 14:13:42 -07:00
Jared Parsons e99dae65fc
Fix a couple of issues (#5188)
Feedback on discord pointed out that there were a few mistakes in the
doc:

- Use `ref field = ref value` instead of `field = ref value`j`. The
latter is correct here because it mimics how `ref` locals work
- Use `readonly ref` to describe `ref` fields that can't be reassigned
instead of `ref readonly`
2021-09-14 12:21:06 -07:00
Bill Wagner 1ea4383736
Clarify matching members prevents synthesized members (#5187)
Fixes #5186
2021-09-14 14:08:44 -04:00
Fredric Silberberg c117a50a14
Added LDM notes for September 13th, 2021. 2021-09-13 15:33:26 -07:00
Rikki Gibson 73dc536d4d
Remove new handling of nullable value types in method type argument inference (#5138) 2021-09-13 11:25:30 -07:00
CyrusNajmabadi 076e25b596
Update using-alias-types.md 2021-09-10 14:56:54 -07:00
CyrusNajmabadi ad321d2aac
Update using-alias-types.md 2021-09-10 13:21:23 -07:00
CyrusNajmabadi 611b638273
Create using-alias-types.md 2021-09-10 11:00:07 -07:00
Julien Couvreur 8aaf7240da
Update README.md 2021-09-10 10:23:53 -07:00
Mads Torgersen 7c9a4c1946
Update LDM agenda 2021-09-10 09:59:26 -07:00
Mads Torgersen cf4d452be2
Update LDM agenda 2021-09-10 09:58:35 -07:00
Bill Wagner f63f70960c
Add missing semicolon (#5165)
See dotnet/docs#26006

Also fixed in dotnet/csharpstandard#371
2021-09-08 09:05:25 -07:00
Yanis Batura 8df1e5d5a7
Update required-members.md (#5147)
fix a typo
2021-09-04 23:29:26 -07:00
Mads Torgersen fc125168ea
Update LDM agenda 2021-09-03 15:39:14 -07:00
Fredric Silberberg fbbdf86e8a
Added field keyword. 2021-09-02 17:46:30 -07:00
Fredric Silberberg c0536b57c7
Add links to the README 2021-09-02 13:56:06 -07:00
Fredric Silberberg dcd6bda4b8
Added notes for August 30th and September 1st 2021-09-02 13:54:25 -07:00
Mads Torgersen 2f9862c8e6
Update LDM agenda 2021-09-02 11:20:37 -07:00
Mads Torgersen 93c42cf98b
Update LDM agenda 2021-09-02 10:58:16 -07:00
Mads Torgersen c8544ccc29
Update LDM agenda 2021-09-02 10:22:35 -07:00
Julien Couvreur ff7e5f04d3
Update README.md 2021-08-30 13:12:11 -07:00
Julien Couvreur f9e8476c97
Update low-level-struct-improvements.md 2021-08-30 11:20:28 -07:00
Mads Torgersen 015894956e
Update LDM agenda 2021-08-29 16:42:53 -07:00
Fredric Silberberg e85356bb95
Added notes for August 25th, 2021. 2021-08-25 14:35:33 -07:00
Fred Silberberg b89d4c9340
Update schedule. 2021-08-24 15:24:59 -07:00
Fredric Silberberg e664481942
Added public issue note. 2021-08-24 15:02:26 -07:00
Fredric Silberberg 7fd92296bc
Added LDM notes for August 23rd, 2021. 2021-08-24 15:00:55 -07:00
Julien Couvreur 86c641d4d5
Update README.md 2021-08-23 11:39:55 -07:00
Bill Wagner a4c9db9a69
Address docs build warnings (#5102)
* fix docs suggestions

There are two different types of changes in this PR:

- H1s should use ATX headers (`#`)
- tables should have a header row that is not empty.

* replace docs links with source

This addresses another docs build warning. The docs build system flags absolute links to the pages on docs.microsoft.com as warnings, which should be replaced with relative links. This fix changes the link to the source doc on GitHub. I'm currently looking for a better solution.

* Revert "replace docs links with source"

This reverts commit b40cfc9632.
2021-08-23 14:33:02 -04:00
CyrusNajmabadi 88825dd480
Merge pull request #5094 from dotnet/Update-raw-string-literal-examples
Update raw-string-literal.md
2021-08-21 10:11:19 -04:00
Mads Torgersen 1d95494330
Update LDM agenda 2021-08-20 12:31:41 -07:00
Charles Stoner 73af544295
Update README.md 2021-08-20 12:17:25 -07:00
CyrusNajmabadi 8255f7d434
Update raw-string-literal.md 2021-08-20 12:09:52 -07:00
Charles Stoner 341d5184ba
Update README.md 2021-08-19 20:50:47 -07:00
Jared Parsons 085024139e
Clarify span safety rules for readonly receivers (#5083)
* Clarify span safety rules for readonly receivers

The compiler excludes receivers from the "Must Arguments Must Match"
rules when the type is a `readonly struct`. This exclusion was not
covered in the rules.

* Apply suggestions from code review

Co-authored-by: Fred Silberberg <fred@silberberg.xyz>

Co-authored-by: Fred Silberberg <fred@silberberg.xyz>
2021-08-19 12:10:53 -07:00
Ruslan Batkaev 9a18b64732
fix typo 'of' -> 'or' (#3026) 2021-08-18 11:23:29 -07:00
Julien Couvreur 6b0f5be210
Update README.md 2021-08-17 22:16:55 -07:00
Fred Silberberg cf9794dbb5
Added another ASAP issue for discussion. 2021-08-17 13:24:11 -07:00
Rikki Gibson 2329ddd55f
Add partial base type nullability differences agenda item (#5069) 2021-08-17 10:17:41 -07:00
Bill Wagner 134702a58a
fix missing links (#5025)
A few links to the standard did not include the folder path.
2021-08-11 10:19:59 -04:00
Rikki Gibson 815cb6903b
Specify when record struct synthesized members are 'readonly' (#4890) 2021-08-10 14:52:53 -07:00
Bill Wagner 0e4b7363ec
fix build warnings in docs (#5018)
Most of the fixes are updating links to sections of the spec so that they work correctly.

Other fixes include semantic markdown:
- exactly 1 H1 heading in each file.
- ATX style headers.
2021-08-10 17:27:10 -04:00
Bernd Baumanns 1714a1ef92
pattern-match-span-of-char-on-string.md is not part of csharp 10 (#5001) 2021-08-05 10:19:29 -07:00
Julien Couvreur 5b473bcf77
Add C# 10 to language history (#4996) 2021-08-04 22:08:23 -07:00
Charles Stoner 4907038e8e
Update lambda / method group natural type to use "function type" (#4728) 2021-08-04 12:15:52 -07:00
Alireza Habibi 2c0481fb22
Update list-patterns.md (#4982) 2021-08-03 10:43:48 -07:00
Fred Silberberg 9a6e23e589
Add topic for when LDM is back. 2021-08-02 13:54:22 -07:00
Julien Couvreur 614edd88d7
Create raw-string-literal.md 2021-07-30 20:59:16 -07:00
Fred Silberberg 1dbcfb485c
Remove no longer needed proposal status checkboxes. (#4977) 2021-07-28 16:14:48 -07:00
Julien Couvreur dcbaa81525
Fix some links (#4976) 2021-07-27 12:34:38 -07:00
Julien Couvreur 1788bfd3ad
Move shipped proposals to csharp-10.0 folder (#4973) 2021-07-27 11:35:55 -07:00
Joseph Musser 9df76ef6c6
Fix typos (#4970) 2021-07-26 19:57:19 -07:00
Fred Silberberg 9f5c1d13c0 Added notes for July 19th and 26th, 2021 2021-07-26 17:46:50 -07:00
Julien Couvreur f4d1c13a6a
Update README.md 2021-07-23 11:46:03 -07:00
Mads Torgersen d3c31779a1
Add dates to LDM agenda 2021-07-22 16:33:38 -07:00
Mads Torgersen f2fe67e0ef
Update LDM agenda 2021-07-22 16:21:33 -07:00
CyrusNajmabadi 31f07a1839
Update README.md 2021-07-22 10:37:59 -07:00
Rikki Gibson fe65068f74
Call EnsureSufficientExecutionStack in PrintMembers (#4951) 2021-07-20 16:47:57 -07:00
Rikki Gibson 9c2ac8391c
Add generic attributes proposal (#4936) 2021-07-20 10:21:17 -07:00
Mads Torgersen f05a7cd15f
Update LDM agenda 2021-07-16 14:58:37 -07:00
WhiteBlackGoose 96e23022d4
A meeting with lambda improvements mentioned (#4931) 2021-07-16 09:19:09 -07:00
Fredric Silberberg 1296da8e31
Fix link 2021-07-13 17:05:27 -07:00
Fredric Silberberg 733f9611aa
Added notes for July 12th, 2021. 2021-07-13 17:04:24 -07:00
Fred Silberberg 4f938f7595
Specify unescaping rules for interpolated strings (#4910)
Co-authored-by: Stephen Toub <stoub@microsoft.com>
2021-07-09 16:42:54 -07:00
Mads Torgersen 136320cab1
Update LDM agenda 2021-07-09 14:37:01 -07:00
Mads Torgersen 7dc4311c7b
update LDM agenda 2021-07-09 13:33:52 -07:00
Julien Couvreur 7e863bee4a
Update async-method-builders.md (#4903) 2021-07-06 10:31:24 -07:00
Fredric Silberberg 70fc3eb703
Clarify that all variance is disallowed on explicit lambda return types. Add email decision on global using warnings. 2021-06-25 10:25:24 -07:00
Fredric Silberberg d91eab3660
Added LDM Notes for June 21st, 2021. 2021-06-23 17:12:18 -07:00
Alireza Habibi e4edc94d14
Use separate list-pattern with square brackets (#4860) 2021-06-22 12:28:33 -07:00
Julien Couvreur a11816a462
Update spec to reflect LDM 2021/06/21 decisions (#4859) 2021-06-21 19:33:30 -07:00
Mads Torgersen 6662672a2b
Update LDM agenda 2021-06-19 01:31:18 +02:00
Charles Stoner b9ea94262d
Update README.md 2021-06-18 07:12:16 -07:00
Mads Torgersen f4265f4899
Update LDM agenda 2021-06-18 15:33:39 +02:00
Meowtimer 22962db0e9
pattern 404 link (#4834)
Should probably link to [csharp-7.0/pattern-matching.md](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.0/pattern-matching.md)
2021-06-16 07:52:19 -07:00
Charles Stoner ef10c625c3
Update README.md 2021-06-15 23:17:49 -07:00
Charles Stoner b518743c6f
Update README.md 2021-06-15 23:17:17 -07:00
Fred Silberberg fb6af9155a
Clarify wording of list patterns conclusion. 2021-06-15 17:04:12 -07:00
Joseph Musser 592da35424
Fix typo (#4831) 2021-06-15 13:43:52 -07:00
Fred Silberberg 258cc959fd
Fix numbering. 2021-06-15 13:27:20 -07:00
Fred Silberberg 36b666765e
Fix default value. 2021-06-15 13:25:35 -07:00
Fred Silberberg 3462d9cd28
Fixed a missing word 2021-06-15 13:12:54 -07:00
Fredric Silberberg ed6ad44e00
Added notes for June 14th, 2021 2021-06-15 11:50:33 -07:00
Julien Couvreur 15c2bca0be
Update README.md 2021-06-15 09:45:42 -07:00
Bernd Baumanns 3ca20d7107
fix: special type implementation for array (#4829) 2021-06-15 08:30:08 -07:00
Julien Couvreur 85ed4b0785
Update README.md 2021-06-11 21:40:13 -07:00
Julien Couvreur 95d0b48db5
Update README.md 2021-06-11 21:31:52 -07:00
Mads Torgersen 20a57a9a6d
Update LDM agenda 2021-06-11 17:24:03 +02:00
Fred Silberberg b282f51ab9
Update the interpolated string handler spec (#4795)
* Update the interpolated string handler spec

Updates the spec to reflect the naming changes, the change in Create vs ctor, and the other open questions that have since been resolved.
2021-06-10 15:11:10 -07:00
Fred Silberberg c618b06a36
Fix parameterless struct constructor example. 2021-06-07 16:57:32 -07:00
Fred Silberberg 900e997f0b
Correct sub-bullets 2021-06-07 15:03:19 -07:00
Fredric Silberberg 9cd6a7d4f7
Added LDM notes for June 7th, 2021. 2021-06-07 15:02:20 -07:00
Julien Couvreur cefa73b371
Update README.md 2021-06-07 12:29:29 -07:00
Mads Torgersen 38845c5639
Update README.md 2021-06-07 12:28:33 +02:00
Julien Couvreur ecd0620606
Relax commitment around single evaluation 2021-06-03 11:06:57 -07:00
AlekseyTs 2a17f2b214
Update "Unresolved questions" section in static-abstracts-in-interfaces.md (#4794) 2021-06-02 18:31:53 -07:00
Fredric Silberberg 4d2b699d44
Added LDM notes for June 2nd, 2021. 2021-06-02 15:01:44 -07:00
Julien Couvreur c4debafa0c
Update README.md 2021-06-02 14:49:29 -07:00
Mads Torgersen 5a1112a255
Update LDM agenda 2021-06-02 07:24:14 -07:00
Julien Couvreur 5cb472d6b4
Split list patterns on enumerables (#4793) 2021-05-30 22:02:01 -07:00
Alireza Habibi bcd1ace777
Update list-patterns.md (#4790) 2021-05-28 15:44:06 -07:00
Fredric Silberberg 32f886de6b
Add missing proposal link 2021-05-27 21:38:37 -07:00
Yair Halberstadt 95d5ea7254
Fix typo (#4788) 2021-05-27 21:36:10 -07:00
Fredric Silberberg 6c911c2b8b
Add LDM notes for May 26th, 2021. 2021-05-27 17:01:07 -07:00
Fred Silberberg 04cb45a8cb
Add open questions for CallerArgumentExpression to the backlong. 2021-05-27 14:46:05 -07:00
Rikki Gibson 74a4b1da81
Adjust 'is' spec (#4785) 2021-05-27 14:18:40 -07:00
Charles Stoner 69be01c864
Update README.md 2021-05-27 11:03:07 -07:00
AlekseyTs bb6eca3925
Propose rules for consuming user-defined conversion operators declared in interfaces. (#4773) 2021-05-27 09:32:52 -07:00
Charles Stoner 6137557e7e
Clarify struct field initialization with default constructor initializer (#4778) 2021-05-26 08:25:23 -07:00
Mads Torgersen c2dfc38cbf
Update LDM agenda 2021-05-24 13:41:52 -07:00
Julien Couvreur b352c17312
Update README.md 2021-05-24 09:31:22 -07:00
Fred Silberberg e178fbbc93
Fix the date 2021-05-20 11:07:02 -07:00
Fredric Silberberg a11db0a6d5
Added LDM notes for May 19th, 2021. 2021-05-20 11:05:20 -07:00
Joseph Musser f751454623
Typo and rendering fixes (#4761) 2021-05-18 16:27:15 -07:00
Fredric Silberberg f817e1f3db
Added notes for May 17th, 2021. 2021-05-18 15:27:11 -07:00
Mads Torgersen 38c9773f13
Add LDM dates 2021-05-14 15:48:14 -07:00
Mads Torgersen 46a2bcff0b
Update LDM agenda 2021-05-14 10:44:15 -07:00
Fredric Silberberg 76538e5e13
Added notes for May 12th, 2021 2021-05-12 16:03:06 -07:00
Mads Torgersen caf3fd7385
Update LDM agenda 2021-05-12 14:11:18 -07:00
Youssef Victor fb699c0c6d
Fix variable name (#4742) 2021-05-11 11:38:16 -07:00
Fredric Silberberg 1a0e4bc758
Update notes branch link. 2021-05-10 15:46:44 -07:00
Fredric Silberberg 3137df7cf4
Added LDM Notes for May 10th, 2021. 2021-05-10 15:45:59 -07:00
Charles Stoner 03aedc0516
Update README.md 2021-05-07 11:21:48 -07:00
Julien Couvreur e6122ed487
Update list-patterns.md 2021-05-05 23:14:57 -07:00
Rikki Gibson e619b20e50
Rename statics-in-interfaces.md to static-abstracts-in-interfaces.md (#4719) 2021-05-04 12:48:39 -07:00
Fredric Silberberg a3ff206420
Added notes for May 3rd, 2021. 2021-05-04 11:45:34 -07:00
CyrusNajmabadi 555704e008
Update README.md 2021-05-04 09:27:36 -07:00
Julien Couvreur 107d8df70d
Update README.md 2021-05-03 14:55:18 -07:00
Julien Couvreur 3c526ee195
Update README.md 2021-05-03 14:54:05 -07:00
Mads Torgersen fd0dc4f157
Update LDM agenda 2021-05-03 09:54:45 -07:00
Charles Stoner 91752413d6
Update README.md 2021-04-30 15:19:35 -07:00
Mads Torgersen 15c507ecc5
Update LDM agenda 2021-04-29 17:58:54 -07:00
Charles Stoner 8777a189c7
Update parameterless struct constructor proposal (#4521) 2021-04-29 11:55:17 -07:00
Fred Silberberg eb30fa570e
Rework improved interpolated string builder proposal with LDM feedback. (#4703)
* Rework improved interpolated string builder proposal with LDM feedback.
2021-04-29 10:40:39 -07:00
Fred Silberberg c4ee96110e
Clarify warning wave on new StructType() 2021-04-29 09:57:31 -07:00
Joseph Musser ed8610b290
Typos (#4712) 2021-04-29 09:14:30 -07:00
Fredric Silberberg 2b70bbed59
Added notes for April 28th, 2021. 2021-04-28 17:09:50 -07:00
Mads Torgersen 97ea12ccd5
Remove Apr 26 LDM 2021-04-23 11:48:35 -07:00
Fredric Silberberg bf7ce619e6
Added LDM notes for April 21st, 2021. 2021-04-23 11:08:15 -07:00
Mads Torgersen 1f655b145c
Add May dates to LDM agenda 2021-04-23 10:45:43 -07:00
Mads Torgersen a4af8a0f8a
Update C# LDM agenda 2021-04-23 10:42:49 -07:00
Julien Couvreur cbc7d18430
Update docs for positional fields and hidden positional members (#4673) 2021-04-23 10:13:05 -07:00
Petr Kulikov f99f391ad9
Fix example: static struct members cannot be readonly (#3354) 2021-04-22 13:14:39 -04:00
AlekseyTs b6f3504bc7
Add Variance safety section to statics-in-interfaces.md (#4610) 2021-04-22 06:59:08 -07:00
Charles Stoner cc6cba159a
Clarify description of inferred type for method group (#4678) 2021-04-20 21:32:43 -07:00
Fredric Silberberg e8b27ef36f
Added LDM notes for April 19th, 2021. 2021-04-20 15:17:38 -07:00
Mads Torgersen 99ee2e877f
Update LDM agenda 2021-04-20 13:05:50 -07:00
Carl Erik Patrik Iwarson 22a8d8976e
Update ranges.md: length -> Length (#4675)
Extremely small fix to get the example code under "Implicit Range support" to run
2021-04-20 09:46:35 -07:00
Charles Stoner 5484a151f7
Update README.md 2021-04-19 21:39:12 -07:00
Charles Stoner f58bfd7a0a
Update lambda improvements proposal (#4642) 2021-04-19 14:39:15 -07:00
96 changed files with 6784 additions and 1632 deletions

View file

@ -1,8 +1,26 @@
Features Added in C# Language Versions
====================
# C# 10.0 - .NET 6 and Visual Studio 2022 version 17.0
- [Record structs](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/record-structs.md) and `with` expressions on structs (`record struct Point(int X, int Y);`, `var newPoint = point with { X = 100 };`).
- [Global using directives](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/GlobalUsingDirective.md): `global using` directives avoid repeating the same `using` directives across many files in your program.
- [Improved definite assignment](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-definite-assignment.md): definite assignment and nullability analysis better handle common patterns such as `dictionary?.TryGetValue(key, out value) == true`.
- [Constant interpolated strings](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/constant_interpolated_strings.md): interpolated strings composed of constants are themselves constants.
- [Extended property patterns](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/extended-property-patterns.md): property patterns allow accessing nested members (`if (e is MethodCallExpression { Method.Name: "MethodName" })`).
- [Sealed record ToString](https://github.com/dotnet/csharplang/issues/4174): a record can inherit a base record with a sealed `ToString`.
- [Incremental source generators](https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.md): improve the source generation experience in large projects by breaking down the source generation pipeline and caching intermediate results.
- [Mixed deconstructions](https://github.com/dotnet/csharplang/issues/125): deconstruction-assignments and deconstruction-declarations can be blended together (`(existingLocal, var declaredLocal) = expression`).
- [Method-level AsyncMethodBuilder](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/async-method-builders.md): the AsyncMethodBuilder used to compile an `async` method can be overridden locally.
- [#line span directive](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/enhanced-line-directives.md): allow source generators like Razor fine-grained control of the line mapping with `#line` directives that specify the destination span (`#line (startLine, startChar) - (endLine, endChar) charOffset "fileName"`).
- [Lambda improvements](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md): attributes and return types are allowed on lambdas; lambdas and method groups have a natural delegate type (`var f = short () => 1;`).
- [Interpolated string handlers](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md): interpolated string handler types allow efficient formatting of interpolated strings in assignments and invocations.
- [File-scoped namespaces](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/file-scoped-namespaces.md): files with a single namespace don't need extra braces or indentation (`namespace X.Y.Z;`).
- [Parameterless struct constructors](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/parameterless-struct-constructors.md): support parameterless constructors and instance field initializers for struct types.
- [CallerArgumentExpression](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/caller-argument-expression.md): this attribute allows capturing the expressions passed to a method as strings.
# C# 9.0 - .NET 5 and Visual Studio 2019 version 16.8
- [Records](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/records.md) and `with` expressions: succinctly declare reference types with value semantics (`record Point(int X, int Y);`, `var newPoint = point with { X: 100 }`).
- [Records](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/records.md) and `with` expressions: succinctly declare reference types with value semantics (`record Point(int X, int Y);`, `var newPoint = point with { X = 100 };`).
- [Init-only setters](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/init.md): init-only properties can be set during object creation (`int Property { get; init; }`).
- [Top-level statements](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/top-level-statements.md): the entry point logic of a program can be written without declaring an explicit type or `Main` method.
- [Pattern matching enhancements](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/patterns3.md): relational patterns (`is < 30`), combinator patterns (`is >= 0 and <= 100`, `case 3 or 4:`, `is not null`), parenthesized patterns (`is int and (< 0 or > 100)`), type patterns (`case Type:`).
@ -64,7 +82,7 @@ Features Added in C# Language Versions
# [C# 7.0](https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/) - Visual Studio 2017
- [Out variables](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/out-var.md)
- [Pattern matching](https://github.com/dotnet/csharplang/blob/master/proposals/patterns.md)
- [Pattern matching](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.0/pattern-matching.md)
- [Tuples](https://github.com/dotnet/roslyn/blob/master/docs/features/tuples.md)
- [Deconstruction](https://github.com/dotnet/roslyn/blob/master/docs/features/deconstruction.md)
- [Discards](https://github.com/dotnet/roslyn/blob/master/docs/features/discards.md)

View file

@ -112,7 +112,7 @@ We should allow expression variables in queries, but keep them scoped to the ind
[csharplang/issues/287](https://github.com/dotnet/csharplang/issues/287)
The [proposal](https://github.com/dotnet/csharplang/blob/master/proposals/caller-argument-expression.md) calls for an extra parameter with a default value (which is then replaced by the expression passed as an argument for the parameter designated in the attribute). This means that in a tie breaker situation, existing methods would match better than new ones that differ only by having this extra argument.
The [proposal](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/caller-argument-expression.md) calls for an extra parameter with a default value (which is then replaced by the expression passed as an argument for the parameter designated in the attribute). This means that in a tie breaker situation, existing methods would match better than new ones that differ only by having this extra argument.
There are solutions to that for API owners:

View file

@ -13,7 +13,7 @@
### Global `using`s
https://github.com/dotnet/csharplang/blob/master/proposals/GlobalUsingDirective.md
https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/GlobalUsingDirective.md
Today we discussed the proposed syntax and restrictions on the current feature specification. As checked in today, the proposal
puts `global` after the `using` directive, which could be potentially ambiguous and require complicated parsing logic, as `global`

View file

@ -17,7 +17,7 @@
### Natural type for lambdas
https://github.com/dotnet/csharplang/blob/master/proposals/lambda-improvements.md
https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/lambda-improvements.md
Today we looked at a proposal around enhancing lambda expressions and method groups with a "natural type", which is helpful for several
ASP.NET scenarios. Overall, the LDM has general support for making enhancements here: explaining how lambdas and method groups don't have

View file

@ -86,7 +86,7 @@ We do like both of these proposals and want them both in the language, but will
### Parameterless struct constructors
https://github.com/dotnet/csharplang/blob/master/proposals/parameterless-struct-constructors.md
https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/parameterless-struct-constructors.md
Finally today, we looked at the proposal for parameterless constructors. This proposal needs to make sure that it can handle what the general
.NET ecosystem can do with struct constructors today, including private struct constructors.

View file

@ -13,7 +13,7 @@
### Parameterless struct constructors
https://github.com/dotnet/csharplang/blob/struct-ctor-more/proposals/parameterless-struct-constructors.md
https://github.com/dotnet/csharplang/blob/struct-ctor-more/proposals/csharp-10.0/parameterless-struct-constructors.md
Today, we took a look at open questions in this proposal.
@ -76,7 +76,7 @@ reflect more nuances, but we approve of the general goal: keep behavior unchange
### AsyncMethodBuilder
https://github.com/dotnet/csharplang/blob/main/proposals/async-method-builders.md
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/async-method-builders.md
We started today by questioning whether we can simplify this proposal a bit. Today, the proposal covers both a scoped version and
a single-method version. The scoped version is complicated, and has a number of downsides:

View file

@ -42,7 +42,7 @@ framework to `Slice` before it ships, would enable the feature in a much more ge
### Lambda improvements
https://github.com/dotnet/csharplang/blob/main/proposals/lambda-improvements.md
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md
After some implementation work, this proposal is back to look at some more changes. One of the big ones is moving the return type to the left
side of the parameter list. This makes the syntax analogous to our other signature declarations in C#, looking like an unnamed version of a

View file

@ -57,7 +57,7 @@ the behavior here as it has also been a source of Roslyn-API consumer confusion
#### Conclusions
We will proceed with `field` as the keyword, and would like to have .NET 6 warning wave to nudge users to write code that has no change of being
We will proceed with `field` as the keyword, and would like to have .NET 6 warning wave to nudge users to write code that has no chance of being
ambiguous.
### Improved interpolated strings

View file

@ -0,0 +1,52 @@
# C# Language Design Meeting for April 19th, 2021
## Agenda
1. [Improved interpolated strings](#improved-interpolated-strings)
## Quote of the Day
- "I'm glad you had a stomachache here so I don't have to"
## Discussion
### Improved interpolated strings
https://github.com/dotnet/csharplang/issues/4487
Today, we spent the full meeting digging into the pros and cons of conditional evaluation of interpolation holes, to ensure that we feel
comfortable with the changes doing so will bring. The real challenge here is that conditional evaluation of interpolation holes will break
with current mental models of interpolated strings: today, interpolated string holes are unconditionally evaluated in lexical order, and
the way the proposal is designed makes it possible for a library update to change whether the expressions in an interpolation hole is
evaluated or not. While library updates can always bring changes in behavior, this is the type of change that is currently only possible
by a library adding the `Conditional` attribute onto a method or changing the type of an exception being thrown, causing a catch handler
to no longer be hit. Adding a new instance method that is preferred over an extension method is similar, but except in rare cases involving
user-defined conversions this can't actually affect the expressions in the method body itself.
If we proceed with conditional evaluation, user education will be a key component. The proposal does not involve conditional evaluation when
the interpolated string literal is used directly as a `string`, but other types can introduce this. If a user is confused by this behavior,
we need a clear thing we can point out to say "this is why you're seeing the current behavior." One potential way to do this is by introducing
a specific syntax to enable conditional interpolation hole evaluation, something like `$?"{ConditionallyEvaluated()}"`. With such a syntax,
we would never conditionally evaluate the holes unless the string were marked with `$?` or `@$?`. However, this immediately becomes a concern
for libraries that want to introduce conditional evaluation: presumably they were making that decision for a good reason. A logging library
would want this to just work, and every library that uses the feature would presumably then also need to make an analyzer to go along with the
feature to ensure their customers are actually using it.
We also considered the benefits that partial evaluation gives the user. An important goal for C# features is that users don't adopt the
feature then immediately switch to something else because it introduces performance issues. Patterns are a good example of doing this right:
the compiler generates code that is usually as good, if not better, than the code the user would write by hand to match a pattern. Today's
interpolated strings, on the other hand, do _not_ do this today. They box value types, don't work with spans, and generate array garbage that
can't be elided. We want interpolated strings to be better: using the language feature should generate code that is just as performant as
you can get manually today. Conditional evaluation is a big part of this: both with the up front check, and with the intervening checks on
each `Append` call, we can ensure that a simple line of C# generates code that is as good as manual checks.
A good analogy to think about for concerns about conditional evaluation is in `struct`s: if C# were to introduce value types today, would
we be concerned that we need to call out the copies everywhere they can occur? Or would we simply accept that, yes, struct copies can happen
and that it's fine. Yes, behavior could change inside method bodies on upgrading a library, but that can happen in a number of ways with any
library upgrade today. New instance methods can be introduced that cause extension methods to longer be chosen, libraries can introduce new
conversions, new exceptions can be thrown/existing exceptions can be no longer thrown. While is another change that can happen, we don't think
it's substantially different enough to warrant concern.
#### Conclusion
We accept conditional evaluation of interpolation holes as written in the spec.

View file

@ -0,0 +1,77 @@
# C# Language Design Meeting for April 21st, 2021
## Agenda
1. [Inferred types for lambdas and method groups](#inferred-types-for-lambdas-and-method-groups)
2. [Improved interpolated strings](#improved-interpolated-strings)
## Quote of the Day
- "And then we will open Pandora's box"
## Discussion
### Inferred types for lambdas and method groups
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md
https://github.com/dotnet/csharplang/issues/4674
Further implementation work has revealed a few potential breaking changes in giving lambdas and method groups a natural type (linked in the issue
above). It is possible to run into these breaks with or without extension methods involved, and while these are the same types of breaks that can
be observed in extension method resolution (adding new ones in a closer scope or adding an instance method that is preferred), these would be a
case of the language actually changing the results of overload resolution for the same inputs. We have a few ideas for how to approach this:
1. Accept the break. This would be one of the bigger breaks that C# has ever taken, on par with the `foreach`-scoping changes in C# 5.
2. Always prefer a target-type for method groups and lambda expressions. This idea, while sounding simple, is quite complex implementation-wise.
The C# type inference and overload resolution algorithms are very much tied to having a natural type for an expression, and flowing that information
through the process. To prefer target-typing here, we would have to first try to target-type, then fall back to a natural type and try the whole
process again. This would add a significant cost complexity to an already-expensive part of the compilation process, and while it might work it
will be an exponentially harder problem every time we want to do something else like this.
3. Make natural types for lambdas and method groups opt-in. This would be through some sort of syntax gesture, such as:
1. A cast like `(_)` to indicate "I would like the natural type for this but I don't want to state it". This would lock in a specific syntax
that would forever need to be explained to users to "use C# 10 please".
2. Only lambdas with return types (indicating new feature uptake) would have a natural type. This would severely hamper the scenarios our
partner teams are interested in: most of the time, their return types should be inferred, and it will not support method groups.
4. Only infer a natural type when converting to `System.Delegate` or `System.MulticastDelegate`. This technically still does have the chance of
breaking scenarios, but the breaks are significantly smaller. However, this would result in the unfortunate consequence that only non-generic
delegate types can cause natural types to be inferred, which feels like a pre-generics solution to the problem.
While accepting a break here could potentially be big, it's important to note that this particular scenario is actually quite fragile today. In
either of the code samples, simply extracting the lambda or method group to a local variable will cause overload resolution to pick the same
overload that giving the lambda a natural type would. This is because the local variable will have a natural type itself. It seems relatively
likely that the only users intentionally taking advantage of this "feature" are creating extension methods that just call the original method
with the delegate, using the extension method as a source of a target-type for the lambda expression.
We also have a decent amount of runway for previewing changes here. We're not planning on shipping this feature in a non-preview version of either
VS or the runtime until it's time to actually release C# 10, so we can make an aggressive version of this change now and see if we need to dial
it back. The preview will have a warning reported whenever the natural type of a lambda or method group is used, which will hopefully give us
enough of a signal during this preview to know just how aggressive we can be here.
#### Conclusion
We will tentatively accept the breaking change to overload resolution. Previews will be used to determine if we need to revisit this decision.
### Improved interpolated strings
https://github.com/dotnet/csharplang/issues/4487
We took a look at a couple of outstanding issues in interpolated strings today. First, we looked at the order of arguments to the builder from the
current context. There is potential here to break lexical ordering of expressions if we allow arguments to the builder's `Create` method to come
after the builder itself in the argument list. While a possible model for thinking of interpolation holes is lambda expressions, where the holes
are either not evaluated or evaluated at a later point from where they were written, these holes can potentially have definite assignment impacts
on the current method body. This is entirely different from other forms of deferred execution in C#, and we're concerned with the implication.
After discussion, we agreed on requiring lexical ordering to be respected: if a parameter is an argument to the `Create` method of the builder type,
it must come before the interpolated string. That error will be reported at the invocation site, and it should be affected by the true lexical
order of the invocation. Named parameters can be used to reorder evaluation, so we should error if named parameter use results in an argument being
evaluated after the interpolated string that needs it. We will implement a warning at the method declaration if the signature of the method requires
named parameters to need to be used at the call site.
We also looked at the proposal around allowing interpolated strings to be "seen" through conversion expressions and through binary addition expressions.
Binary addition being proposed resulted from prototyping work in the runtime to use the builder, while the conversion proposal was a logical next step.
After discussion, we don't think that the use case of needing to disambiguate between two overloads with different builder types and otherwise identical
signatures is realistic. We accept the use case for binary addition, but we will only do it for conversions to interpolated builder types as we don't
have an ask for `IFormattable` or `FormattableString` here.
#### Conclusions
Lexical ordering should be respected, and seeing interpolated strings through binary addition expressions will be supported.

View file

@ -0,0 +1,125 @@
# C# Language Design Meeting for April 28th, 2021
## Agenda
1. [Open questions in record and parameterless structs](#open-questions-in-record-and-parameterless-structs)
2. [Improved interpolated strings](#improved-interpolated-strings)
## Quote of the Day
- "I was kinda hoping you'd fight me on this"
## Discussion
### Open questions in record and parameterless structs
https://github.com/dotnet/csharplang/blob/struct-ctor-more/proposals/csharp-10.0/parameterless-struct-constructors.md
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/record-structs.md
#### Initializers with explicit constructors
The first thing we need to resolve today is a question around our handling of field initializers, both in the presence of an
explicitly-declared constructor(s), and when no constructors are present. There's two parts to this question:
1. Should we synthesize a constructor to run the field initializer? and
2. If we don't, should we warn the user that their field initializer won't be run?
We feel that a simple rule here would be to mirror the observed behavior with reference types: if there is no explicit constructor
for a struct type, we will synthesize that constructor. If that constructor happens to be empty (as it would be today in a
constructorless struct type because field initializers aren't yet supported) we optimize that constructor away. If a constructor
is explicitly declared, we will not synthesize any constructor for the type, and field initializers will not be run by `new S()`
unless the parameterless constructor is also explicitly declared. This does have a potential pit of failure where users would
expect the parameterless constructor to run the field initializers, but synthesizing a parameterless constructor would have bad
knock-on effects for record structs with a primary constructor: what would the parameterless constructor do there? It doesn't
have anything it can call the primary constructor with, and would result in confusing semantics. To address this potential issue,
we think there is room for a warning wave dedicated to using `new S()`, when `S` does not have a parameterless constructor. There
will be some holes in this, particularly around generics: `struct` today implies `new()`, and we're concerned about how breaking
the change would be if we tried to make the warning apply to everywhere that a parameterless struct was substituted for a type
parameter constrained to `struct`. We would also have to take another language feature to enable `where T : struct, new()`, which
isn't allowed today. If there is future appetite for introducing another warning wave to cover the generic hole, we can look at
it at that point.
##### Conclusion
We will only synthesize a constructor when the user does not explicitly declare one. We will consider a warning wave when using
`new` on a struct that does not have a parameterless constructor and also has an explicit constructor with parameters.
#### Record struct primary constructors
Next, we looked at how the parameterless struct constructor feature will interact with record primary constructors. The rule we
decided for the first question ends up making the decisions very simple here:
1. Parameterless primary constructors are allowed on struct types.
2. The rules for whether an explicit constructor needs to call the primary constructor are the same as in record class types. This
applies to an explicit primary constructor too, just like in record class types.
##### Conclusion
Use the above rules.
#### Generating the Equals method with `in`
We looked at a potential optimization around the `Equals` method, where we could generate it with an `in` parameter, instead of
with a by-value parameter. This could help scenarios with large struct types get better codegen. However, when we would want to do
this is a very complicated heuristic. For structs smaller than machine pointer size, it is usually faster to pass by value. This
gets even more complex when considering types that are passed in non-standard registers, such as vectorized code. We don't think
there's a generalized way to do this heuristic. Instead, we just need to make sure that the user can perform this optimization, if
they want to.
##### Conclusion
We won't attempt to be smart here. Users can provider their more customized equality implementation if they so choose.
#### `readonly` Equality and GetHashCode
Finally in record structs, we looked at automatically marking the `Equals` and `GetHashCode` methods as `readonly`, if the all the
fields they use for the calculation are also all `readonly`. While this would be technically feasible, we're not sure what the
scenario for this is, beyond just marking the entire struct `readonly`. At that point, every method would be readonly, including
the ones we synthesize.
##### Conclusion
We don't do anything smart here. Users can just mark the struct as `readonly`.
### Improved interpolated strings
https://github.com/dotnet/csharplang/issues/4487
We're getting close the end of open questions in this proposal. We looked at 2 today:
#### Confirming `InterpolatedStringBuilderAttribute`
An internal discussion on simplifying the conversion rules resulted in a proposal that we only look for the presence of a specific
attribute on a type to determine if there exists a conversion from an interpolated string literal to the type. This simplification
results in much cleaner semantics: the presence of various methods on the builder type no longer plays into whether the conversion
exists, only whether conversion is valid. This will help users get understandable errors that don't silently fall back to
string-based overloads when a builder doesn't have the right set of `Append` methods to lower the interpolated string.
We also looked at whether we should control the final codegen based on a property on the attribute: we initially proposed conditional
evaluation could be controlled by the attribute. This would potentially let the compiler change the behavior of when expressions in
interpolation holes are evaluated: up front, or in line the `Append` calls. However, the logic for determining the codegen is not
as simple as one property: the `Append` calls can potentially return `bool`s to stop evaluation, and the `Create` method can
potentially `out` a parameter to control this as well. There are valid scenarios for all 4 possibilities here, so a switch that only
allows either all conditional or no conditional isn't a good option. It's also complex because this would be a library author making
a lowering decision for user code. We also note that, if we decide that this is important at a later date, we can extend the pattern
then.
##### Conclusion
Using the attribute is accepted. The `Append` and `Create` method signatures will drive the lowering process.
#### Specific extensions for structured logging
Finally today, we looked at a question around including specific syntax to support structured logging. [Message templates](https://messagetemplates.org/)
are a well-known structured logging format that most of the biggest .NET logging frameworks support, and as we want these libraries
to consider using the improved interpolated strings feature, we looked to see if we can include specific syntax to help encourage
this. After some initial discussion, our general sentiment is that this feels too narrow. An example syntax we considered is
`$"{myExpression#structureName,alignment:format}"`, and while this would work for the scenario, it wouldn't be a meaningful improvement
over simply using a tuple expression: `$"{("structureName", myExpression),alignment:format}"`. It is possible to construct interpolated
string builders that only accept tuples of `string` and `T` in their interpolation holes, and with the other adjustments we made today
there should be good diagnostics for such cases. Further restrictions can be imposed via analyzers, as is possible today. While a more
general string templating system could be interesting, we think that this is a bit too narrow of a focus for a language feature today.
##### Conclusion
We won't pursue specific structured syntax at this time.

View file

@ -0,0 +1,94 @@
# C# Language Design Meeting for May 3rd, 2021
## Agenda
1. [Improved interpolated strings](#improved-interpolated-strings)
2. [Open questions in record structs](#open-questions-in-record-structs)
## Quote of the Day
- "You all have a special way of working"
## Discussion
### Improved interpolated strings
https://github.com/dotnet/csharplang/issues/4487
Today, we got through the last of the open questions around the improved interpolated strings feature proposal.
#### `Create` vs `.ctor`
The first open question is around using the `Create` method instead of a constructor call. The spec today uses a static `Create` method as
an abstraction, to allow a hypothetical builder type to pool reference types in a static context, rather than being forced to return a `new`
instance on every builder creation. However, while this is a nice abstraction, we don't know of any proposed builder types that would take
advantage of this feature, and there are real codegen benefits to using a constructor instead. If we later come across a use case that would
like a static `Create` method instead, we can also add it later by using a switch on the attribute.
##### Conclusion
For better codegen, we will go with a single method of creation, a constructor, not a `Create` method.
#### Converting regular strings to builder types
We could consider any `string` value as being convertible to a builder type automatically via the builder pattern, which is the trivial case
of `value.Length` number of characters with zero interpolation holes. If we were to do this, it would be in the interest of consistency: a
user could more readily reason about how all string types will interact with an API. However, we do already have a mechanism in the language
to enable this: implicit user-defined conversions. Pushing a string through the standard builder pattern will likely be worse for performance
than what an API-author would write in an implicit conversion, and user-defined conversions already have well-defined semantics and impacts
on overload resolution.
##### Conclusion
Rejected. If an API author wants string to be convertible to their builders, they can use a user-defined conversion.
#### `ref struct` builder types in `async` contexts
Today, `ref struct` types are generally forbbiden in `async` contexts. This is because `await` expressions can occur in any subexpression, and
we have not formalized rules around ensuring that a `ref struct` variable does not cross an `await` boundary. In order to enable `ref struct`
builder types in `async` methods, we'd need to do essentially that same work in order to make usage safe. We're not introducing any new tradeoffs
by avoiding this: already today, developers need to choose between using a `ref struct`, and allowing usage in `async` contexts. While we would
be interested in general rules here, we don't think we should do anything special with regard to interpolated strings builder types. If we enable
it in general, interpolated string builders will naturally come along for the ride.
##### Conclusion
We will treat interpolated string builder usage as any other usage of a `ref struct` in `async` methods (today, that means disallowed).
### Open questions in record structs
#### Implementing `ISpanFormattable`
The BCL is adding a new interface for .NET 6, `ISpanFormattable`, which allows types to be formatted directly into a span, rather than having to
create a separate string instance. One immediate thought we have is why are records (struct or class) special here? We often think of records as
being named versions of anonymous types/tuples, so if we were to do this for records we would likely want to do this for those types as well.
This is important because it does impact public API: new overloads of `PrintMembers` will need to be implemented, and how this will interact with
the existing `PrintMembers` method that takes a `StringBuilder` is unclear. We also don't have a clear scenario here: `ToString()` in records is
mainly focused around debugging and logging scenarios. While performance is a nice to have here, it's not an overridding concern, and users who
want to actually use `ToString()` to do more important work can provide their own implementation, and `ISpanFormattable` with it. They can handle
the problems around interop with existing APIs by simply not using those APIs, which will keep the considerations simpler for them.
##### Conclusion
Rejected.
#### Using `InterpolatedStringBuilder` in record formatting
We could potentially optimize the implementation of `ToString()` by using `InterpolatedStringBuilder` as the string-building mechanism, rather
than `StringBuilder`. Unfortunately, this again runs into the problem of needing to interop with existing code, which unfortunately limits our
options here. Without a clear scenario to try and solve, we don't think this is worth it.
##### Conclusion
Rejected.
#### Passing an `IFormatProvider`
We could potentially generate an overload of `ToString` that takes an `IFormatProvider` and passes it to nested contexts. This again runs the
question of lacking a clear scenario and interop with existing code. We additionally don't have a clear idea of when we'd pass the format
provider to a nested type, as there is no standardized `ToString(IFormatProvider)` method on all types. If a user wants to have formatted
strings in their records, they presumably are providing their own implementation anyway, so we don't feel this is appropriate.
##### Conclusion
Rejected.

View file

@ -0,0 +1,95 @@
# C# Language Design Meeting for May 10th, 2021
## Agenda
1. [Lambda improvements](#lambda-improvements)
## Quote of the Day
- "I'll allow it"
## Discussion
### Lambda improvements
#### Lambda natural types in unconstrained scenarios
There are a few related open questions around how we handle lambda expressions and method groups in unconstrained scenarios. These
are scenarios such as:
```cs
var a = (int x) => x; // What is the type of A?
void M1<T>(T t);
M1((int x) => x); // What is the type of T?
void M2(object o);
M2((int x) => x); // What is the type of the expression? ie, o.GetType() returns what?
```
These scenarios are all related to how much we want to define a "default" function type in C#, and how much we think doing so could
stifle later development around value delegates (if we even do such a feature). In C# today, we have 3 function types:
* Delegate types that inherit from `System.Delegate`.
* Expression tree types that inherit from `System.Linq.Expressions.**Expression`.
* Function pointer types.
All of these types support conversions from some subset of method groups or lambdas, and none is currently privileged above another.
Overloads between these types are considered ambiguous, and users must explicitly include information that tells the compiler what
type of function type to use, such as by giving a target type. If we allow `var`, conversions to `object`, and/or unconstrained generic
type inference to work, we would be setting in stone what the "default" function type in C# is. This would be particularly noticeable
if our future selves introduced a form of lightweight delegate types and wanted to make them the default in various forms of overload
resolution. We are doing analogous work with interpolated strings currently, but interpolated strings are a much smaller section of
the language than lambda expressions, and lambda irregularities are potentially much more noticeable.
We could protect our future selves here by making the spec more restrictive: Do not allow lambdas/method groups to be converted to
unconstrained contexts. This would mean no `var`, no unconstrained type parameters, and no conversion to `object`. We would only infer
when there was information that we could use to inform the compiler as to which type of function type to use: conversion to just
`System.Delegate` would be fine, for example, because we know that the delegate type version was being chosen. While this would protect
the ability to introduce a value delegate type later and make it the default, we see some potential usability concerns with making
such a delegate type the default. At this point, we believe such a delegate type would be based on ref structs, and making `var`
declare these types would be minefield for async and other existing scenarios. Using such types by default in generic inference would
have similar issues around ref struct safety rules. And finally, if such a type were converted to `object`, it would by necessity need
to be boxed somewhere, obviating the point of using a lightweight value delegate type in the first place. Given these concerns, we
believe that we would just be protecting our ability to make the same decision later, and that another function type would not be a
good "default".
##### Conclusion
We allow inferring a natural type for a lambda or method group in unconstrained scenarios, inferring Action/Func/synthesized delegate
type, as appropriate.
#### Type inference
We went over the rules as specified in the proposal. The only missing bit is that, at final usage, a function type needs to look at
constraints to determine whether it should be inferred to be a Delegate or an Expression.
##### Conclusion
Accepted, with the additional step around constraints.
#### Direct invocation
Finally today, we looked at supporting direct invocation of lambda expressions. This is somewhat related to the
[first topic](#lambda-natural-types-in-unconstrained-scenarios), but could be implemented even if we chose to do the restrictive version
of that issue because this feature would not require us to actually choose a final function type for the lambda expression. We could
just emit it as a method and call it directly, without creating a delegate instance behind the scenes at all. However, we don't have an
important driving scenario behind this feature: technically it could be used as a form of statement expression, but it doesn't feel like
a good solution to that problem. The main thing we want to make sure works is regularity with other inferred contexts:
```cs
// If this works
var zeroF = (int x) => x;
var zero = zeroF(0);
// This should also work
var zero = ((int x) => x)(0);
// But this wouldn't work with var, so is it fine to have not work here?
var zero = (x => x)(0);
```
##### Conclusion
We generally support the idea, even the final example. It may take more work however, and thus may not make C# 10. We'll create a
more formal specification for approval.

View file

@ -0,0 +1,95 @@
# C# Language Design Meeting for May 12th, 2021
## Agenda
1. [Experimental attribute](#experimental-attribute)
2. [Simple C# programs](#simple-c-programs)
## Quote of the Day
- "I've always felt the shotput with the Rubik's Cube should be part of the Olympics"
## Discussion
Today we took a look at a couple of broader .NET designs that will impact C#, to get an idea of how we feel C# fits into these designs.
### Experimental attribute
https://github.com/dotnet/designs/blob/main/accepted/2021/preview-features/preview-features.md
The first proposal we looked at is the design the runtime team has been working on around preview features. The C# compiler has shipped
preview features in the compiler ahead of language releases before, but this hasn't been very granular, and we've never shipped a version
of the runtime (particularly not an LTS!) where some parts of the runtime and language features built on top are actually still experimental.
Support stories get complicated, particularly when you start mixing installations of LTS versions of .NET 6 and .NET 7 on the same machine.
For example, we'll be shipping a preview of abstract static members in interfaces in .NET 6, but this implementation will _always_ be a
preview. It's possible that, for .NET 7, we'll move the feature out of preview, and the version of the C# compiler in VS installed at that
point would no longer consider abstract statics to be a preview language feature, even if the project itself is targetting .NET 6. To solve
this, the runtime will introduce a new attribute, detailed in the preview feature proposal, which marks APIs and elements of RuntimeFeature
that should be considered preview in this version of the runtime, and then require that the consuming library be marked preview itself.
Where this requirement comes from is one of the open questions in this meeting: should it come from an analyzer or from the compiler itself?
The analyzer approach is initially attractive because it is easier to implement. Roslyn's analyzer infrastructure, particularly with the
investments around both the symbol analyzer model and IOperation, means that it is possible to implement this analyzer almost entirely
language-independently, while a compiler feature would have be implemented in each compiler separately. It is also significantly easier to
maintain an analyzer: turning off analyzer errors is possible for false-positives while bugs are patched, while compiler errors are impossible
to disable. However, this flexibility does come with downsides: it's possible to disable the analyzer for _real_ positives and ship in an
unsupported state, and potentially cause downstream dependencies to take advantage of preview features unintentionally. There's also no
guarantees that users actually enable the analyzer, as they might just disable analyzers for any particular reason. Despite these concerns,
we think that an analyzer is a good path forward, at least initially. We can use an analyzer in .NET 6 to iron out the semantics of how the
feature works, and look at putting the logic into the compiler itself in a future version.
In particular, there are some interesting semantics that still need to be worked out. Should APIs be allowed to introduce or remove previewness
when overriding or implementing a member? For example, `System.Decimal` already has a `+` operator today, and it will be implementing the
preview numeric interfaces that define the `+` abstract static operator. The `+` on System.Decimal is not preview today, nor should it be in
.NET 6, but it _will_ be implementing the preview `+` operator. Explicit implementation is also not always possible for these operators, as
we will have asymmetric operator support in the math interfaces that get unified with a symmetric operator later in the hierarchy, so we
can't rely on enforcing explicit implementation to solve this problem. On the opposite side of this problem, allowing adding of previewness
at more derived API level is problematic, because the user could be calling a less-derived method and therefore accidentally taking advantage
of a preview feature.
Finally, there is some IDE-experience work to think through. While we do want these APIs to show up in places like completion and refactorings,
we would also like to make sure we're not accidentally opting users into preview features that then break their build totally unexpectedly. We
think there is design space similar to our handling of `Obsolete`, such as marking things preview in completion lists.
#### Conclusion
We will proceed with an analyzer for now and look to move that logic into the compiler at a later point, if we think it makes sense. More
design is needed on some parts of the feature, but it doesn't require direct involvement of the LDM.
### Simple C# programs
https://github.com/dotnet/designs/pull/213
Finally today, we looked at "simple" C# programs. We take simple here to mean programs that don't have a lot of complex build logic, and are
possibly on the smaller side code-wise. Simple does _not_ necessarily mean "Hello, World!" and nothing else. While we are interested in the
intro experience, we additionally think there is opportunity to expand to address a market that has traditionally had a bunch of friction to
use C# in today.
C# has historically had a focus on very enterprise scenarios: professional developers working on larger projects using a dedicated IDE. Our
user studies have shown that this workflow isn't what many newer users are expecting, particularly if they're coming from scripting languages
like Go, Javascript, or Python. These users instead expect to be able to simple make a `.cs` file and run it, with potentially more ceremony
as they start adding more complex dependencies or other scenarios. Other expectations exist (such as repls), but our studies have shown that
this is the most popular. An important part of our task here will be figuring out where that "more ceremony" step lies, what that additional
ceremony will look like, and how it will interact with any additions we make to the language (how project-file based projects interact with
`#r` directives, for example).
Investing in tooling that puts NuGet directives in C#, as well as potentially `#load` or other similar file-based directives, is going to
necessitate that we reconcile file structure and project structure in C#. Today, the contents of C# files are very intentionally independent
of their locations on disk: file structure is handled by the project file, and project structure is handled by `using` directives and
namespace declarations. `#r` to local NuGet packages or `#load` to add other `.cs` files would blur the line here.
Another important consideration of these directives is how complex we want to let them get. At the extreme end, these directives could be
nested inside `#if` directives, which starts to necessitate a true preprocessing step that the SDK tooling will need to understand and perform.
Today, preprocessor directives in C# don't have massive effects, and they can be processed in line with lexing. There are restrictions we can
look at for where to allow `#r` and similar directives, and deciding on those restrictions will help inform where exactly that additional
ceremony will land. For example, we could require that all of these directives must be the first things in a file, with nothing preceding
them. This would ensure that conditional NuGet references are that cliff of complexity that requires a project file.
Finally, how much of the project settings do we think is reasonable to control in a .CS file? Is it reasonable to set language version or TFM
in a file? What about output settings such as dll vs exe, or single-file and trimming settings? We don't have answers for these today, and
some of these answers will be driven by discussions with the SDK teams, but they're all part of determining where the cliff of complexity
will land.
#### Conclusion
Overall, we're extremely excited to take this challenge on, and look forward to working through this design to find the edges.

View file

@ -0,0 +1,125 @@
# C# Language Design Meeting for May 17th, 2021
## Agenda
1. [Raw string literals](#raw-string-literals)
## Quote(s) of the Day
- "You have a mongolian vowel separator"
"Dear god"
- "I was expecting [redacted] to flip the table and say I'm out of here"
"When you flip the table at home, how do you clean that up?"
"Exactly, I don't want to have to clean that up"
"Wait wait, that sounds like a new teams feature"
- "Pretty easy to interpolate those results"
"Is that a correct use of the word interpolate?"
"No"
## Discussion
https://github.com/dotnet/csharplang/issues/4304
Today we took a look through the proposal for raw string literals. This would add a new type of string literal to C#, specifically
for working with embedded language scenarios that are difficult to work with in C# today. A number of other languages have added
similar features, though our colleagues on Java are perhaps the most direct inspiration for this proposal (particularly with the
whitespace trimming feature).
Overall, the LDM is universally in favor of the feature. Some details still remain to be worked out, but we believe this is a feature
that will benefit C#, despite the complexity in adding yet another string literal format.
### Single-line vs multi-line
The proposal suggests both single-line and multi-line forms for this new literal format. While multi-line literals are the obvious
headline feature here, we do think there's a use-case for a single-line form: embedded languages like regex are often single-line,
used inline in a method call or similar. They suffer from all the same problems as other embedded languages today (frequent need
for quotes, but using quotes is both hard and results in hard-to-read code).
#### Conclusion
We would like a single-line version.
### Whitespace trimming
Verbatim string literals today have behavior that make them somewhat unpleasant for multiline strings because, if whitespace is not
desired at the start of the line, the literal content has to be fully start-aligned in the file. This leads to a zig-zag code pattern
in the file which breaks up the flow of the file and can make subsequent indentation hard to judge. Trimming solves this by removing
whitespace from the start of every line, determined by the whitespace preceding the final quotes. This feature has a couple of levers
we can tune to increase or decrease the requirements on the user, namely around handling blank lines. The proposal currently says
that blank lines must have a _prefix_ of the removed whitespace on the line. That means that, if the whitespace to be removed is
`<tab> <tab> <space>`, a completely empty line is fine, as is a blank line with `<tab> <tab>`. Both are prefixes of the whitespace to
be removed. However, a line with `<tab> <space>` is _not_ fine, because that is not a prefix of the whitespace to be removed. We
could make this more strict by saying that a blank line must either have the full whitespace to be removed, or no content at all.
While this is more strict, it could end up being a better user experience: whitespace is, by nature, hard to visualize, and providing
a good diagnostic experience around "this whitespace isn't a prefix of that whitespace" is not something we're eager to tackle. On the
other hand, trailing whitespace is pretty easy to accidentally insert today, and we don't make that a compile error by default anywhere
else.
#### Conclusion
We'll go with the stricter behavior for now: blank lines must either contain the entire whitespace being removed, or no text at all.
We can relax this later if it makes the experience better.
### Language fences
We also looked at allowing a language fence in literals, similar to how markdown works. In multiline markdown strings, ```` ```identifier ````
marks the code block as having a specific language, which the markdown renderer can use to render the text inline. In C# string literals
today, there's fragmented support for this implemented in a number of tools: VS, for example, detects when a string is being passed
directly to a regex API, and also has support for a comment syntax to turn on highlighting for different locations. We could standardize
this for C# raw string literals: the proposal is specifically worded such that text is _required_ to start on the line after the open
quotes to allow us to include this feature in the future. One immediate question, however, is how would we support this for single-line
literals. Our existing syntax highlighting support is specifically for regex, which is the exact use case for the single-line version,
but the language specifier doesn't work there. We could potentially support a trailing language specifier for this case, such as
`""".*"?"""regex`, but it would limit the number of things that can be put in the space.
#### Conclusion
We're mixed on language fences, leaning against supporting them for now. More debate is needed.
### Interpolation
This proposal didn't originally have interpolation, but after a large pushback from the community, interpolation was added. Because the
goal of the proposal is to represent all strings without escaping, the immediate next question is how we represent interpolation holes
without requiring escaping of braces. The proposal suggests we use the number of `$`s in front of the raw string to represent the number
of braces required to trigger an interpolation hole: `$$"""{}"""` would be the string `{}`, because `{{}}` is needed to be counted as an
interpolation hole. IDE experience is going to be very important here: context-sensitive interpolation holes are going to be somewhat
harder to keep track of, and refactorings to add additional braces to an existing string if needed will be very helpful for users to avoid
tedious and potentially error-prone manual changes when suddenly the user needs to use the existing number of braces as an actual string
component.
We also considered a slightly different form, `$"""{{`, where the number of braces after the triple quotes controls how interpolations work.
This form, while providing a more direct representation of the number of curlies required, doesn't work for single-line strings and cannot
be applied to all interpolated strings. We further thought about using the number of quotes to control both the number of braces required
for interpolation holes and the number of quotes required to close the string; while this would work for the single-line form, it would
require that all interpolation holes are a minimum of 3 braces, but most scenarios we can think of either don't need braces, or only need
to represent a single open/close brace. It also cannot be extended to all interpolated strings.
#### Conclusion
We want interpolation, and we're ok with using the number of `$`s to control the number of braces.
### Alternative Quotes
We considered whether to use ```` ``` ```` or `'''` instead of or in addition to `"""`. The `` ` `` symbol is concerning because it can
be hard for non-English keyboard layouts to hit; while there are symbols in C# today that are already difficult for these layouts to hit,
we don't want to deliberately introduce more pain for these users. We also don't like the complexity of either having multiple symbols
that can start strings in C#: this proposal is already adding an axis of complexity (for gain we feel is worth it), but we don't think
the additional axes of complexity is worth the tradeoff here. It does mean that the single-line version cannot represent a string that
starts with a `"`, but we think this is an OK tradeoff.
#### Conclusion
Quotes are the only way to start strings. Users that need to start a string with a quote must use the multi-line version.
### Miscellaneous conclusions
Even though we could support parsing long strings of quotes on a non-blank line inside a raw string literal, we will require that if a
user wants to use 4 quotes in a string, the raw string delimiters must be at least 5 quotes long.
Strings like this are supported:
```cs
var z = $$"""
{{{1 + 1}}}
""";
```
The innermost braces are the interpolation holes, the resulting value here would be `{2}`.

View file

@ -0,0 +1,121 @@
# C# Language Design Meeting for May 19th, 2021
## Agenda
1. [Triage](#triage)
1. [Checked operators](#checked-operators)
2. [Relaxing shift operator requirements](#relaxing-shift-operator-requirements)
3. [Unsigned right shift operator](#unsigned-right-shift-operator)
4. [Opaque parameters](#opaque-parameters)
5. [Column mapping directive](#column-mapping-directive)
6. [Only allow lexical keywords](#only-allow-lexical-keywords)
7. [Allow nullable types in declaration patterns](#allow-nullable-types-in-declaration-patterns)
2. [Protected interface methods](#protected-interface-methods)
## Quote of the Day
- "I feel like I've known this before and then I packed it away in some chamber of horrors in my brain"
## Discussion
### Triage
#### Checked operators
https://github.com/dotnet/csharplang/issues/4665
There are some complexities to this proposal (such as betterness rules between checked and unchecked operators), how it will affect VB, and some
potential clunkiness in the interface definitions (`int` shift doesn't overflow today even in checked contexts, but would need to expose both?),
but we think that this is pretty essential to a well-formed generic math interface structure.
##### Conclusion
Triaged into the working set.
#### Relaxing shift operator requirements
https://github.com/dotnet/csharplang/issues/4666
We have some immediate visceral reactions to this, but it's been 21+ years of BCL and other library design and we don't see huge abuse of other
operators. It might be time to lift the restriction here.
##### Conclusion
Triaged into the working set.
#### Unsigned right shift operator
https://github.com/dotnet/csharplang/issues/4682
This is an odd missing operator in general, and a hole in our design for generic math that similar libraries in other languages have filled.
##### Conclusion
Triaged into the working set.
#### Opaque parameters
https://github.com/dotnet/csharplang/issues/4629
We're not a huge fan of how this silently munges with the method signature, and the number of cliffs there are: what happens when a user wants
2 of these parameters with the same type, or wants to add multiple constraints to a type parameter?
##### Conclusion
Rejected. It's possible we could revisit flavors of this with associated types at a later point, but as is this is rejected.
#### Column mapping directive
https://github.com/dotnet/csharplang/issues/4747
We have a few open questions, such as whether we need an end offset as well. However, overall this looks good. These directives are basically
never human-written or read, and it helps solve problems for partner teams using C# as a DSL.
##### Conclusion
Triaged into the working set.
#### Only allow lexical keywords
https://github.com/dotnet/csharplang/issues/4460
This is a discussion that has been building in the LDM for years, particularly around older contextual keywords such as `var` and `dynamic`
used in a type position. We think there are two broad categories of contextual keywords here: keywords that we think are "safe" to reserve,
such as the aforementioned `var`, that are used in positions where standard C# conventions wouldn't allow things to be named in a conflicting
manner: types, for example, are PascalCase by convention in C#, and `var` starts with a lowercase character. Making a break here helps _both_
the compiler team and the average language user, as it simplifies the language and isn't likely to break code that isn't intentionally trying
to avoid the feature. There are other keywords though, such as `yield`, that we think are good to keep as contextual keywords. It makes the
compiler team's life more difficult, but it helps users, and we don't want to make changes here just to make the compiler's job a bit easier.
We think there's opportunity to do work here in the first set, particularly if we take a phased approach where we warn about a keyword in C#
X and then totally deprecate in Y.
##### Conclusion
Triaged into the working set. We'll revisit soon to think about the phased strategy and see what we want to do for C# 10.
#### Allow nullable types in declaration patterns
https://github.com/dotnet/csharplang/issues/4724
There are some really gnarly parsing ambiguities here, but even if we could solve the compiler parsing problem, the human parsing problem will
remain. We don't think those problems are really solveable, and that the gain isn't worth the complexity.
##### Conclusion
Rejected.
### Protected interface methods
https://github.com/dotnet/csharplang/discussions/4718
When DIMs were initially implemented, we were concerned about a `public` member in a type named the same as a `protected` member in an interface
that type is implementing being confused for an implicit implementation of that `protected` member. However, this ends up somewhat hindering the
goal of DIMs in the first place, which was making adding new methods to an interface not be a breaking change. Given this, and given that the
future option for _having_ implicit `protected` interface member implementation was already removed in V1 of DIMs, we'd like to remove this
restriction. We don't think we can treat this as a bugfix, despite the low impact nature, as it was intentional and C# 8 has been out for a year
and a half now. The hardest part will be coming up with a name for the compiler error message: perhaps "Relaxed DIM requirements for non-public
members".
#### Conclusion
We'll relax this restriction as a new language feature.

View file

@ -0,0 +1,90 @@
# C# Language Design Meeting for May 26th, 2021
## Agenda
1. [Open questions in list patterns](#open-questions-in-list-patterns)
## Quote of the Day
- "If somebody lays down on the track I'm perfectly willing to be taken hostage"
## Discussion
### Open questions in list patterns
https://github.com/dotnet/csharplang/blob/main/proposals/list-patterns.md
Today we looked at a number of open questions in list patterns, currently being implemented.
#### Patterns allowed after a slice operator
We first revisited the question of what to allow after a `..` pattern. Previously we stated that any possible pattern
should be allowable in this position; however, there is some concern that this could be confusing to code readers. We
forsee the vast majority of patterns in this location being simply capturing the slice into a pattern, and there is a
dissimilarity here with other containing pattern contexts. For positional, property, list, and length patterns, nested
patterns are all visually inside delimiters (`()`, `{}`, and `[]`, respectively). Here, the pattern would be on the
slice element, but _not_ visually inside. There is also some concern that perhaps we should take the position that using
a nested pattern here is just generally not good form: we think that such syntax will become quickly unreadable, going
against the general goal of the feature. There's also potential for being visually hard to parse: something like
`..var x and not null` parses very differently in a pattern (as `..(var x and not null)`) than it does in regular syntax,
for something like `..1+2` (which parses as `(..1)+2`).
Looking at these concerns gives us 3 general options for how to move forward:
1. Simplify the syntax, and only allow `..identifier`. Users can omit the type entirely. General patterns are not allowed.
2. Only allow `..var pattern`. This has symmetry with other declaration patterns.
3. Allow all patterns.
Option 1 is initially somewhat attractive because it will simplify the 99% case here. However, we have some concerns: it's
not regular with other declarations, and some users do not like implicitly-typed variables. It also makes it harder to
change our minds here in the future, if we ever wanted to add an `all` or `any` pattern. Meanwhile, version 2 suggests
a pattern generality that would not exist, and (to the user that runs into this) for seemingly arbitrary reasons.
Given our concerns with the first two approaches, we think that, despite earlier concerns, approach 3 is the way to go.
We can take a stronger stance in the IDE and our documentation with fixers and best practices to help ensure that nested
patterns don't get too crazy here.
##### Conclusion
Original design upheld, any pattern is allowed after a slice.
#### Multi-dimensional array support
Currently, the list pattern specification has an open question as to whether MD-arrays should be supported. One of our
goals for this feature was to bring symmetry between object creation and deconstruction/pattern forms, and array/collection
initializers work with MD-arrays today, suggesting they should be supported in patterns. However, our current rules depend
on types being indexable/countable/sliceable, which MD-arrays are not today. They're also generally not a huge area of
investment for either the language or the runtime, so it feels odd to make sure they work here but not also with the rest of
the indexing/counting/slicing features.
##### Conclusion
Not supported. If we want to make a general MD-array focused release, we would want to revisit all the areas they're currently
lacking, not just list patterns.
#### Binding rules for Slice
This is a double-sided question on how we bind `Slice` methods: should we allow default parameters, and should we allow
extension methods for Slice? In general, we think we should follow the same rules as the existing support around slicing,
which is no on both questions. We can look at a general feature to loosen the restrictions as a separate proposal.
##### Conclusion
No default parameters or extension methods.
#### Tests emitted for `{ .. }`
The main question here is whether `{ .. }` should emit a check that `Length` or `Count` is greater than or equal to 0, or if it should
simply be a no-op. This must be specified in the language, as not doing so would result in
`default(ImmutableArray<int>) is { .. }` having undefined behavior (it will throw or not, depending on whether `Length` is
evaluated). We think that `..` without a trailing pattern and without any other element patterns is similar in concept to a
`_` pattern: `default(ImmutableArray<int>) is { Length: _ }` does not actually evaluate `Length`, and thus won't throw.
The `..` is the same: _other_ things cause `Length` to be evaluated, such as the precense of other element patterns or a
length pattern. A slice pattern can change the nature of that check (moving it from an `==` to a `>=`), but it doesn't add
the check on its own. Similarly, `.._` will not call slice, just like other discard operations. This does mean that, for
list implementations that do not follow framework design guidelines and return negative lengths or counts, `{ .. }` will not
fail when they do so, but we think that is fine.
##### Conclusion
`{ .. }` will not emit a `Length` check, and `{ .._ }` will not call `Slice`.

View file

@ -0,0 +1,125 @@
# C# Language Design Meeting for June 2nd, 2021
## Agenda
1. [Enhanced #line directives](#enhanced-line-directives)
2. [Lambda return type parsing](#lambda-return-type-parsing)
3. [Records with circular references](#records-with-circular-references)
## Quote of the Day
* If you make it `..`, does that mean the end is included or not?
## Discussion
### Enhanced #line directives
https://github.com/dotnet/csharplang/issues/4747
This proposal covers a set of enhancements for the `#line` directive that solve several problems that the razor team has had over the years with
the existing directive causing mismapping of code in the debugger. While the razor team is the main user and driver of this scenario, these enhancements
will help any DSL authors that embed C# in their code and want to have a debugging experience. The issue today is that nested expressions don't have
sequence points emitted correctly, and putting the expression on a newline is often not a sufficient solution to ensure that they end up correctly
represented.
We brainstormed a few different ways to determine the span of an expression for this mapping. Inherently, the enhanced directive needs to be able to
specify the length of the mapped line, because users might end up putting multiple separate fragments of C# code on the same line in the original text
file. To be able to represent that accurately, we therefore need to ensure that the exact length is represented. We considered whether we could do this
via the total length of the mapped expression, rather than specifying the end line/column of the directive. However, there are a few concerns with that
approach:
1. Files are often written and checked into source control with different encodings/line endings, most commonly differing between `\r\n` and `\n` for
line endings. For maps that span multiple lines, therefore, the total character count could end up being wrong after compilation depending on what system
created the mapping and what built it.
2. For humans attempting to author and debug generators that use these directives, line and column are easier to map to the real code that they're writing.
Every editor includes this information when editing a file based on the current cursor location. This makes understanding what is happening easier.
We also considered how the line and column information is defined. Today, C# in general defines specific characters that are newlines (in part to ensure
that the existing `#line` directives are well-defined). This doesn't change with the new proposal, and some editors don't always agree on what constitutes
a new line (such as vertical tab), this proposal doesn't change these inconsistencies. For character offsets, we define it as UTF-16 characters, following
with the rest of the .NET ecosystem here. If the ecosystem ever wants to attempt to change to a different encoding, it will be a much larger effort and
adjusting the semantics of `#line` should be trivial by comparison.
Finally, we looked at a few different syntax options for the directive, as we felt that just 5 numbers next to each other was unnecessarily difficult to
read:
1. `#line (10,20)-(11,22) 3 "a.razor"`
2. `#line (10:20)-(11:22) 3 "a.razor"`
3. `#line 10:20-11:22 3 "a.razor"`
4. `#line 10,20-11,22 3 "a.razor"`
5. `#line (10,20)-(11,22):3 "a.razor"`
6. `#line (10,20) (11,22) 3 "a.razor"`
7. `#line (10,20):(11,22) 3 "a.razor"`
8. `#line (10,20)..(11,22) 3 "a.razor"`
We think options without the parens are a bit confusing, because `-` binds more tightly than `,` or `:` in c#, making it look like `10 , (20-11) , 22`.
We also think that `,` is the more common line/column separator. Given this, we'll go with option 1.
#### Conclusions
Existing proposal is accepted with the following clarifications:
1. Column info is described as UTF-16 characters.
2. The syntax is `#line (number , number) - (number , number) number string`.
#### Lambda return type parsing
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md
In a previous LDM, we decided that lambda return types should go before the parameter list, as in `ReturnType (ParamType param) => { ... }`. This has
presented some challenging parsing scenarios.
```cs
F(b ? () => a : c); // Conditional expression or lambda that returns 'b?'?
```
This issue is very similar to generics and greater-than/less-than parsing, and can benefit from the same solution: we specifically define a set of
characters after the lambda body in the language and use them to determine, at that point, whether the expression is a ternary or a lambda with a return
type. For example, if token after the lambda is a `)`, it couldn't have been a ternary. This will take effort, but should be doable.
Another parsing problem comes from multiplication:
```cs
F(x * () => y) // Is it multiplication, or a lambda with a return type of x*?
```
Here, our saving grace is that `*` binds more tightly than the lambda expression today, so this code does not parse. This should allow us to disambiguate
the cases here. Again, this will take effort, but should be doable.
While considering these cases, we also thought about lambda naming as a potential easier disambiguation point. We could allow lambdas to be named (perhaps
just with a `_` to start), and require them to have a `_` to use the return type. Further, we need to consider this now because the `identifier () => {}`
syntax can only mean one thing: `identifier` will either need to be the return type, or the name of the lambda. As we continue to narrow the gap between
lambda expressions and local functions, it stands to reason that we may at some point want to give them names (despite the phrase "named lambda" being a
bit of an oxymoron). Further, choosing to make the single-identifier case mean the type instead of the name breaks with the precedent we have around lambda
parameters, where the name can be specified without the parameter type.
On the other hand, we do feel that the return type is the more common thing to want to specify for a lambda. While named lambdas could be useful for recursive
scenarios, we don't see this as a driving need, while the return type is explicitly being driven to enable .NET 6 scenarios. Additionally, if we allowed
lambda names for recursion we'd sign ourselves up for recursive type inference, as we'd have to start inferring the return type of lambdas that can call
themselves. By saying that the single-identifier case is for specifying the return type, we separate that out into a different feature, and we have a natural
type to allow as the return type of the lambda for this case: `var`. In order to protect this design space, we'll disallow `var` as the explicit return type
of a lambda expression.
#### Conclusion
We think the existing difficulties should be possible to parse, with a bit of effort. We'll reserve `var` as an explicit return type to protect our design
space around recursive type inference in lambdas, if we ever decide to try and tackle that challenge.
### Records with circular references
https://github.com/dotnet/roslyn/issues/48646
In the brief time left at the end of the meeting, we wanted to re-examine our previous position around circularity in record types. There are several ways
that users can end up with circular data structures in records, in both obvious (and detectable) ways of direct mutable recursion, such as in a doubly-linked
list, and indirectly, via nested mutation. Our initial position in records was that this isn't something we want to try and solve in the language, but could
be solved via source generators. There are a number of points where users might want to customize aspects of record code generation, such as using a different
equality comparison option for strings, or doing sequence/set equality for collections. The question we want to answer, therefore, is whether we should move
the cliff to generators up a bit further and add a method of excluding specific fields from record semantics to the language, and whether we should add a new
warning wave warning to inform users when they're setting themselves up for direct recursion issues. This warning can't be perfect, however: it cannot catch
mutually-recursive types where one type has a mutable field of the other type, particularly when fields of a non-sealed type are involved. For example, record
`A` has a mutable field of type `IDisposable`. Record `B` has an immutable field of type `A` and implements `IDisposable`. `A` is constructed, then `B` is
constructed with that instance of `A`, then `A`'s mutable field is set to `B`. Unless we warn on every mutable field of a non-sealed type, that case is
impossible to detect, and we're cautious of overwarning and then causing users to miss real issues.
#### Conclusion
A smaller working group will meet to explore this in more depth, and make a recommendation to the LDM about the approach we should take.

View file

@ -0,0 +1,116 @@
# C# Language Design Meeting for June 7th, 2021
## Agenda
1. [Runtime checks for parameterless struct constructors](#runtime-checks-for-parameterless-struct-constructors)
2. [List patterns](#list-patterns)
1. [Exhaustiveness](#exhaustiveness)
2. [Length pattern feedback](#length-pattern-feedback)
## Quote of the Day
- "They should probably be told off, I was going to say something else, but they should be told off in code review"
## Discussion
### Runtime checks for parameterless struct constructors
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/parameterless-struct-constructors.md
In testing parameterless struct constructors, we've found a new bug with `Activator.CreateInstance()` on older frameworks. This code
will print `True, False` on .NET Framework 4.8:
```cs
System.Console.WriteLine(CreateStruct<S>().Initialized); // True
System.Console.WriteLine(CreateStruct<S>().Initialized); // False (On .NET Framework)
T CreateStruct<T>() where T : struct
{
return new T();
}
struct S
{
public readonly bool Initialized;
public S() { Initialized = true; }
}
```
This is due to a caching bug in the framework, at it essentially means that `Activator.CreateInstance()` will only correctly invoke a
struct constructor the first time it is used, and any subsequent calls in the same process will run the constructor and then zero-init
on top of that value, so side-effects (such as `Console.WriteLine`) would be observed, but field initialization would not. This is similar
to the bug that sunk parameterless struct constructors the last time we attempted to add them, but .NET Core and 5 do have the correct
behavior here. As .NET Framework is not a supported target platform for C# 10, however, we don't think this is a showstopper like it would
have been back in C# 6. We think that this is a good place for an analyzer to warn consumers, as the compiler itself doesn't want to try
and infer what framework a user is targetting based on the presence of APIs, and runtime feature checks in the compiler are always hard
errors and cannot be worked around.
#### Conclusion
An analyzer will be incorporated into the default analyzer pack to warn users about use of parameterless struct constructors on older,
unsupported framework targets.
### List patterns
https://github.com/dotnet/csharplang/issues/3435
#### Exhaustiveness
We have a couple of test examples that add some exhaustiveness complications:
```cs
_ = list switch
{
{ .., >= 0 } => 1,
{ < 0 } => 2,
{ Count: <= 0 or > 1 } => 3,
};
```
This switch expression should be considered exhaustive, but it will require understanding that the `>= 0` in the first pattern _can_ apply
to element 0, and that the last expression should then handle all the rest of the cases. Another similar case is this one:
```cs
_ = list switch
{
{ .., >= 0 } => 1,
{ ..{ .., < 0 } } => 3,
};
```
While this example is a bit silly, it illustrates the general thing we want to: list patterns on a slice should count towards the containing
list pattern for exhaustiveness. While there is the possibility that this means a slice could give bad results (ask for a slice from x to y,
get the wrong thing back), we generally consider such types to be intentionally subverting user expectations. A list pattern would not be the
only place where such a type mislead a user, and we don't think there's a reasonable way to protect against such types.
##### Conclusion
We should make exhaustiveness work for these scenarios.
#### Length patterns
We've heard from a number of our more heavily-invested users through channels such as Twitter, Discord, Gitter, and GitHub Discussions that
our existing plan for length patterns have been generally confusing and/or actively misleading. The major issues that have been raised:
1. While there is symmetry with array size specifiers in construction, that symmetry doesn't carry to any other collection creation.
2. The `[]` syntax is most often used for accessing at an index, which length patterns don't do.
3. It is extremely tempting to do `{}` as the empty list pattern as a reduction of the rest of the patterns.
We've brainstormed a few ways to try and address various parts of this feedback:
1. Have a special `length` pattern that can be used in a list pattern: `{ 1, 2, 3, length: subpattern }`. This pattern can only be used in
list patterns, so the empty list would be `{ length: 0 }`. This can help with issues 1 and 2, but not with 3.
2. Go back to square brackets, as in the original proposal. This helps with all 3 issues, but reintroduces the new issue that `[]` isn't
symmetric with array and collection initializers.
3. Use parens/positional patterns. This seems interesting, but has a problem because positional patterns will check for `ITuple` on inputs
today.
4. Require using indexers in nested patterns: `{ [0]: pattern, [2]: pattern }`. This is understandable, but extremely verbose.
5. A more general version of 1: just allow combining list and property patterns into one `{}`. We could potentially have a separator token
such as `,` or `;`, which would allow us to have a pretty simple empty list pattern of `{ , }` or `{ ; }`.
We didn't get too deep on any particular syntax with the time remaining, but we are convinced that we need to take another look at these.
##### Conclusion
A smaller group will hammer out a proposal and bring it back to LDM. We need to take the time to get this right, which may mean that the feature
slips and does not make C# 10.

View file

@ -0,0 +1,197 @@
# C# Language Design Meeting for June 14th, 2021
## Agenda
1. [Open questions in CallerArgumentExpressionAttribute](#open-questions-in-CallerArgumentExpressionAttribute)
2. [List pattern syntax](#list-pattern-syntax)
## Quote of the Day
- "My first reaction is that I thought we got rid of the C# test team for testing esoteric scenarios"
## Discussion
### Open questions in CallerArgumentExpressionAttribute
https://github.com/dotnet/roslyn/issues/52745#issuecomment-849961999
https://github.com/dotnet/csharplang/issues/287
#### VB Support
Conclusion: yes, we should support VB here.
#### Generated code
This question centers around this example:
```cs
void M([CallerMemberName]string arg1 = "1", [CallerArgumentExpression("arg1")]string arg2 = "2")
{
Console.WriteLine(arg2); // What gets printed?
}
void M2()
{
M();
}
```
As we see it, there are 5 possible values that a reasonable programmer could expect.
1. `null`
2. The empty string: `""`.
3. The default value of `arg1`, as an expression: `"\"1\""`.
4. The default value of `arg2`: `"2"`.
5. The value filled in for `arg1`, as an expression: `"\"M2\""`.
We don't think option 1 is useful here, as the parameter is attributed to not accept `null`, and this
would just mean that every use of `CallerArgumentExpression` would be required to handle the `null` case.
We also don't think that options 3 or 5 are really correct either: the attribute here is about providing
the specific syntax the user used, not the _value_ the user used. There are many ways to express the values
given as a constant value: we could just turn `"M2"` into a string, or we could say `"\"" + "M" + "2" + "\""`.
Both are technically correct, but neither reflects what the user actually wrote. Finally, for option 3, we
think that this is trying to second-guess the user. They provided a default value for the parameter, and if
we never respect that value then the default value was useless. Given these, we think the correct approach
is option 4.
##### Conclusion
Option 4: the default value of the parameter will be used. We will not turn compiler-generated code into
equivalent C# expressions.
#### Self-referential arguments
Consider these examples:
```cs
void M3([CallerArgumentExpression("arg1")]string arg1 = ""); // Warning?
M3(); // What gets passed? null? ""?
```
##### Conclusion
We think this is absolutely worth a warning in source code, and if in metadata then we should just provide the
default value of the parameter.
#### Span of the expression
Consider this example:
```cs
M(arg1: /* Before */ "A" + /* Mid */ "B"
/* After */); // What is passed for arg2?
```
There are 3 possible answers for this:
1. The argument expression should refer to the start `arg1:` to the end of the position, either `)` or `,`,
depending on whether the argument is followed by another or not.
2. The argument expression should refer from just after `arg1:` to the end of the position, not including the
argument specifier.
3. We should ignore any trivia, and just have the expression span from the start of the real C# executable code
(the string `"A"`) to the end of the real executable C# code (the end of `"B"`).
While there are legitimate argument for 1 or 2, we don't think they provide enough benefit to make up for the fact
that they will be including leading and trailing whitespace that we don't believe is useful for the users of this
attribute. Given this, we think option 3 is the correct way forward.
##### Conclusion
Option 3: we go from the start of real C# executable code to the end of the expression, not including any leading
or trailing trivia.
### List pattern syntax
https://github.com/dotnet/csharplang/issues/3435
#### Revisiting syntax
We've heard a lot of community feedback around our existing proposal for length patterns, which looks like this:
```cs
_ = list is [0]; // List has length 0;
```
Top among user feedback is that this syntax is:
1. Confusing. Even among users who tend to give the LDM the benefit of the doubt with syntax choices, we've heard
vociferous feedback that this is not clear and that there is not a clear enough parallel to array creation length
specifiers to make this obvious.
2. Unnatural for the base case. The traditional recursive pattern that languages with strong pattern matching
constructs use is some number of cases that pull out interesting bits, and then a base case to handle the empty list.
Unfortunately, `{ }` is _not_ the empty list case, despite being what otherwise appears to be an empty list pattern.
While in some cases this happens to work because all that's left to handle is when the input is non-null, we don't
think it will lead to clear code.
A smaller group met to try and brainstorm some approaches to solving the issue. These are:
1. Return to the original proposal syntax, using square brackets (`[]`) to denote a list pattern. This breaks with
the correspondence principle, but it does have stronger parallel with other languages, has a natural base case, and
we could potentially add a new creation form that achieves correspondence (and take the time to address things like
`ImmutableArray<T>`, which cannot be initialized by collection initializers today).
2. Use a separator at the end of a list pattern, such as `;`: `{ 1, 2, 3; } or { ; }`. This separator would be
required, giving a few advantages:
1. Because the separator is always required, the base case looks like the shortest version of the pattern.
2. Allows list and property patterns to be combined into a single block.
3. Gives us an avenue to allow collection and property initializers in the same block, by reusing the same syntax
later.
3. Keep the status quo. Users will get used to the syntax.
These suggestions led to spirited debate. An unfortunate truth here is that, no matter what approach we take, we have
discrepency with some aspect of the language. The semicolon separator approach allows us to mostly keep in line with
collection initializers, but the trailing `;` being required is very different and a wart. Square brackets, on the other
hand, are _very_ different from the rest of C#. Today, square brackets are used for indexing operations and for
specifying the length of an array. Nothing in C# uses them to denote a group of things that is a collection. There are
proposals to use these brackets for an improved version of collection initializers though, giving us an opportunity for
future fulfillment of the correspondence principle, even if it won't be fulfilled on initial release. Patterns also
already have some discrepency with the rest of C#, particularly around `and/or/not` patterns, which aren't words used
in the rest of the language.
##### Conclusion
We will go with option 1: using square brackets for the list pattern. We still need to decide if and how these can be
combined with recursive patterns, but it gives us the most flexibility with regard to future regularity in the language.
#### Length patterns
Orthogonally, we have also come up with a few suggestions for the length pattern:
1. Recognize a special `length` keyword as a property pattern: `{ length: 10 }`. When a type is Countable, this
property is available, and it will bind to `Length` or `Count` as appropriate.
2. Recognize the `Length` and `Count` properties on:
1. Types that are countable
1. Types that are both countable and indexable
3. Keep the status quo.
Given that we've chosen square brackets for our new list pattern syntax, option 3 is out. This leaves us with option
1 or 2. We originally wanted special length patterns in the language because we wanted list patterns to work on a type
that didn't have a `Length` or `Count` property: `IEnumerable`. While we still want to do this, the implementation work
is quite complex and we think that it might not get into the initial version. So, while we're not ruling out 1, we don't
think it's necessary quite yet.
Option 2 is nice, but it has a couple of wrinkles. First, it's a breaking change, because we specially recognize that
the property in question cannot be negative. This can affect flow analysis and introduce warnings or errors about
unreachable patterns, and remove warnings about non-exhaustive switch expressions. It's not pretty, but we think we can
tie this recognition to a warning wave. It will be the first time a warning wave _removes_ warnings, instead of adding
them, but we think it's the right move. Second, what types should we specially recognize here. Countable is a very broad
definition in C#: it pretty much just means has an accessible property named either `Count` or `Length`. We think that's
too broad for general recognition; while collections should never have negative lengths, the word `Count` or `Length` on
its own is not strong enough evidence that the type is a collection. Instead, we think we should require both countable
and indexable, the same requirements for using a list pattern in the first place. This will ensure that the type at least
behaves like a collection, and while there still might be such types that return negative `Count`s or `Length`s, patterns
are only one place where such types will confuse their users and we don't think it's an edge case that should derail the
whole feature.
##### Conclusion
We will specially recognize the `Count` and `Length` properties on types that are both countable and indexable, assuming
that it can never be negative.
#### Timing
Given that the changes we've made today are specifically driven by community feedback, we feel that this feature needs
more bake time than is left in the C# 10 cycle. The feature will ship in preview, either with C# 10 (like static abstracts in
interfaces) or shortly after 10 is released. We want to make sure that the course-corrections we're making here help
community understanding of the feature, and we don't have enough time before C# 10 is released to implement the changes
and get them in customer hands before 10 is declared final.

View file

@ -0,0 +1,173 @@
# C# Language Design Meeting for June 21st, 2021
## Agenda
1. [Open questions for lambda return types](#open-questions-for-lambda-return-types)
2. [List patterns in recursive patterns](#list-patterns-in-recursive-patterns)
3. [Open questions in async method builder](#async-method-builder-overrides)
4. [Email Decision: Duplicate global using warnings](#email-decision-duplicate-global-using-warnings)
## Quote of the Day
- "[redacted] is suddenly very blue." "They're only transmitting the blue channel." "They don't like where this is going."
## Discussion
### Open questions for lambda return types
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md
#### Method type inference from return types
The question here centers on the following scenario:
```cs
static void F<T>(Func<T, T> f) { ... }
F((int i) => i); // ok (in C# 9 and 10)
F(i => i); // error (in C# 9 and 10)
F(int (i) => i); // what should the behavior in C# 10 be?
```
The reason for this is that the way anonymous method inference is worded, we specifically pay attention to types specified in source.
The question here, then, is whether we should get information from an explicit return type of a lambda, and if so what type of inference
we should make?
For the first question, we think the answer is pretty simple: the user explicitly put a type in, so we should see it. Anything else will
make the feature feel incomplete, and will likely end up being a breaking change _somehow_ if we were to do it later.
For question 2, we looked at the current behavior for parameters. Today, we make an _exact_ inference from explicit lambda parameter types
to type parameters, rather than an upper bound inference. This means that the type parameter must exactly match the lambda parameter type,
rather than allowing any kind of variant conversion in that position. We could potentially loosen this restriction in a future version (and
VB.NET actually does this already), but we do question the merits of the feature a bit: why would a user be using a different type here?
The lambda is only being used in this one location, so it should just be using the type it needs. We can squint and see a few potential use
cases, but they don't rise to the 100 points for us at the moment. Therefore, to be consistent with parameters, we'll make an exact inference
for lambda return types.
This also needs to apply for other variance scenarios, such as lambda->delegate type conversions:
```cs
Func<object> f = string () => "";
```
This will also require an exact match, and thus the above code will error.
##### Conclusion
Variance is disallowed for explicit return types in lambdas. This means that type inference will make an exact inference from explicit return
types to type parameters in that position, and variant conversions will be disallowed for lambda->delegate conversions.
#### Parsing ref return types
We have a question on whether ref return types will need to be surrounded by parens to make parsing work. For example:
```cs
Delegate d1 = (ref int () => x); // ok
Delegate d2 = ref int () => x; // Currently parses as ref (int () => x)
Delegate d3 = ref b ? ref int () => x : ref int () => x; // What should this mean?
```
This seems like a problem we should be able to fix with some clever grammar rules. Semantically, the idea of `ref`ing a lambda doesn't make
much sense, as `ref`s should point to locations, and lambdas are not a location.
##### Conclusion
Lets do the fancy grammar work to make this parse like the user would want.
### List patterns in recursive patterns
https://github.com/dotnet/csharplang/issues/3435
Last week's discussion left us with a strong conclusion about the general form of list patterns: they will use `[]` as the list pattern
syntax, so matching an integer list with the numbers 1-3 would be `list is [1, 2, 3]`. However, we still need to address where a list
pattern will be allowed. We have a few proposals:
1. Do not put the list pattern in a recursive pattern at all. To combine a list pattern and a property pattern or type pattern, `and`
will need to be used.
2. Put the list pattern after the property pattern in regular recursive patterns, and require that a property pattern is used (even if
empty) when combining a list pattern with either a type pattern or a positional pattern.
3. The same as 2, but only require the property pattern for the actually ambiguous case of the empty list pattern.
4. Have a separate list pattern, and also allow the list pattern as an optional trailing component to a property pattern. The effect
of this version is a combination of 1 and 2.
One concern with the versions that allow property patterns to be directly combined with list patterns is that it recomplicates recursive
patterns, after C# 9 decomplicated them by allowing the type pattern to be used on its own. We don't think that the combination of type
and list patterns is going to be a common case, and we therefore have concerns about the special disambiguation rules that they would
need as a part of recursive patterns as it would just never become a common enough case for users to internalize the rules here. This
would make both reading and writing such patterns less straightforward. However, if we're wrong about the commonality of this combination,
then requiring an `and` pattern everywhere would also get tedious and repetitive. Ultimately, we think we need to get a version of this
into preview first and see if users actually end up needing the combination patterns. To get the best feedback, therefore, we'll go with
option 1 for now, and if we hear complaints about needing to use `and` to combine various patterns with list patterns frequently we can
revise our approach.
Given that we are choosing to go with 1, we also looked at whether to allow a trailing designator after the list pattern. This starts
to get to a slippery slope: why allow trailing designators but not property or type patterns? However, it is our job as the LDT to
determine how far down a slippery slope we want to go, and stop there. We think that, unlike property or type patterns, designators are
going to be an extremely common case for list patterns, particularly in nested contexts, and we already have other feedback from patterns
to add more designators in places (such as after a `not null` pattern). Given this, we'll add an optional designator after list patterns.
#### Conclusion
List patterns will be their own pattern type, not part of recursive patterns, and will have an optional trailing designator.
### Open questions in async method builder
https://github.com/dotnet/csharplang/issues/1407
#### Async method builder override validation
The first question today is whether we should validate that the return type of a method or lambda marked with `AsyncMethodBuilder` is
actually task-like. For example, should this be legal:
```cs
[AsyncMethodBuilder(typeof(MyCustomBuilder))]
public async int M() => ...;
```
Our options here are:
1. Require that the return type be task-like, even though we won't use the information from that type.
2. Do not validate that the return type is task like. This will even include lambdas with an inferred return type.
3. Do not validate, but require that lambdas marked with this attribute have an explicit return type.
We don't like number 2 because the user is saying what builder type the compiler should use, but not actually saying what type the
builder is building. This feels backwards. We also think 1 is too restrictive: the user has to make the type a task-like type, but
then the compiler discards all the information from that type when actually emitting the method. Given these, our preference is 3.
##### Conclusion
We will not validate that the return type is task-like, but when applied to a lambda the lambda must have an explicit return type.
#### Async method builder on public members
For task-like types, we have a requirement that the builder type must have the exact same accessibility as the task-like type itself,
to ensure that users don't run into scenarios where the task-like type is not usable in an async context despite having the attribute.
This isn't a concern for the attribute on a method, however, because the attribute doesn't affect consumers of the method. It only
affects the codegen for the method itself. These attributes are also not inherited, so if an override would like to use the same
builder type it will have to manually apply the attribute itself. Given that, we think that it should be fine if the visibility of
the builder type is not the same as the visibility of the return type, so long as both are visible at to the method using those types.
##### Conclusion
Accessibility of the builder type does not need to match the accessibility of the return type in method contexts.
#### Factory indirection
The existing proposal has an indirection for allowing a factory to create the builder, which the compiler then uses in the method
body. We don't have a use case for this at the present, so we will remove it from the spec to simplify the proposal.
##### Conclusion
Factory indirection is removed.
### Email Decision: Duplicate global using warnings
Some early feedback on global usings has suggested that duplicated using warnings can be painful, particularly if a global using and
a non-global using are duplicated. Code generation can very easily run into this scenario, and it doesn't have an easy resolution.
After discussion, we decided to remove the duplicated using warning for when a global using directive or a regular using directive are
duplicated. The compiler will still issue a hidden diagnostic to inform the IDE and analyzers, but there will be no user-facing warning
by default. We will still error when the same alias is defined more than once, even if the alias refers to the same type, as we feel
that aliases are more like declarations and we believe declarations should be intentional.
#### Conclusion
Duplicate using warning is removed when a global using is duplicated by another using (global or otherwise).

View file

@ -0,0 +1,90 @@
# C# Language Design Meeting for July 12th, 2021
## Agenda
1. [C# 10 Feature Status](#c-10-feature-status)
2. [Speakable names for top-level statements](#speakable-names-for-top-level-statements)
## Quote of the Day
- "You do realize there was a quote of the day there when you said there are still holes in the interpolated strings feature?" _collective groan_ "I keep track of the important stuff"
## Discussion
### C# 10 Feature Status
https://github.com/dotnet/roslyn/blob/main/docs/Language%20Feature%20Status.md#c-next
We spent the first half of today going through the remaining features that are currently being worked on by the compiler team and prioritizing them for what is going to make
C# 10, and what will need to be pushed out to early preview for C# 11. Of the things still under C# Next, the current statuses are:
- Parameterless struct constructors - will be merged for 17.0p3. This includes integration with record structs.
- nameof(parameter) - This is becoming increasingly important as we add new features that reference other parameters in attributes. Nullable already existed, but we're additionally
adding `CallerArgumentExpression` and `InterpolatedStringHandlerArgument` in C# 10. However, it is unlikely to be prioritized enough for C# 10.
- Parameter!! - This has IDE impact, so while the compiler-side is basically done, it does need some more work. We additionally want more community bake time to let customers actually
use the feature before we ship it. We'll be aiming for early C# 11 previews.
- Relax ordering of partial and ref - This is not being worked on, and should be removed from the status page.
- CallerArgumentExpression - The feature work for this is very nearly done. Should be in for C# 10.
- Generic attributes - The feature work for this is complete. Needs a few more tests, then should be merged for C# 10.
- List patterns - We're aiming for an early preview in C# 11.
- Remaining lambda work - Wave 1 (conversion to Delegate/Expression and attributes) and wave 2 (explicit return types) have been merged. We are currently working on Wave 3 (natural type and synthesized delegate types). Should be in for C# 10.
### Speakable names for top-level statements
When we initially shipped top-level statements in C# 9, we made the decision that the type and method that the statements generated should be unspeakable. Our reasoning here was that
the only reason the type or method would be needed would be for reflection to obtain the assembly, and users could use another type in the assembly for this. However, as work continues
on the lighter ASP.NET feather templates, we are encountering scenarios where there actually _aren't_ any other types in the user's assembly, which makes obtaining the assembly for
unit testing very complex. To address this, we considered a mechanism to make the type and method speakable by user code. We have a few main axes of choice for a proposal, several of
which are tied together:
* Introducing a named type into a compilation could break that compilation. Do we want to make it conditional on whether the user already has a type with the name we choose?
* Do we want to tie generating the type to language version or not?
* Will we allow the type to be speakable from the current compilation, or only from assemblies that _reference_ the current compilation?
* Will the type be partial or not?
* Do we want just the type to be speakable, or the type and the method both?
* Do we want the type to be public or internal?
First, we looked at the breaking change. We believe we would name the type `Program` and the method `Main`, following long C# template tradition, so our real question is "who is going
to create a program that is using top-level statements and has a type named `Program`?". We believe this to be a sufficiently small number of users that we are ok with the breaking
change. At the same time, we also looked at whether we should only generate the type when the user's language version is set to the appropriate version, meaning that C# 9 would still
generate an unspeakable name. After some consideration, we don't think that it's worth it to have separate generation strategies, as it will complicate the code for very little chance
of breaking. It does mean that there is the potential for someone to upgrade the compiler and not the language version and observe a breaking change from a different assembly, but we're
ok with this.
Next, we looked at the various levels of speakability. Only allowing the type to speakable by external assemblies is the minimal change to make, but it feels oddly inconsistent. Similar
reactions were had for the method name: while we don't have a specific scenario currently for the method naming being speakable, we feel it would simplify the mental model for either
everything to be speakable, or nothing to be speakable. We've always had the view that top-level statements are simply a sugar over a Main method in some type, and now we solidify
that view by adjusting the sugar to this:
```cs
using System;
Console.WriteLine(GetInt());
static int GetInt() => 1;
```
desugars into:
```cs
using System;
partial class Program
{
public static void Main()
{
Console.WriteLine(GetInt());
static int GetInt() => 1;
}
}
```
By only specifying `partial class`, the type defaults to `internal` visibility, and we allow any other `partial` parts to be explicit about the visibility of the type and change it
however they choose. We will also change the signature of main based on the content, as we already do today with `args` and `await`.
#### Conclusion
We will adopt the above desugaring for top-level statements. This will introduce a break for any programs that have a non-partial `Program` type in the global namespace, or have a
partial `Program` type in the global namespace that is either not a `class` or defines a method named `Main` if that program is using top-level statements.

View file

@ -0,0 +1,39 @@
# C# Language Design Meeting for July 19th, 2021
## Agenda
1. [Global using scoping revisited](#global-using-scoping-revisited)
## Quote of the Day
- "Any guesses on how many tries it took for me to spell that correctly?" "Well, you misspelled it."
## Discussion
### Global using scoping revisited
https://github.com/dotnet/csharplang/issues/3428
The SDK team is currently considering how and where they will generate default global usings for .NET 6, and we want to take another look at our decisions around the
[scope of global usings](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-15.md#global-usings). Previously, we decided that global usings should
be thought of as being copied and pasted into every file, and that there is no separate outer scope for global usings. However, there are some type names that are relatively
common in .NET, such as `Task`, that would likely be part of a default set of SDK global usings (as `using System.Threading.Tasks.Task;` is another way of saying "Use C# 5
features please"). If there is no separate global using scope for these types, however, we'd introduce ambiguities for these types.
There are a number of complexities to the separate scope idea that would be equally as painful for users, in potentially subtler ways. In particular, extension method lookup
across multiple sets of usings can be tricky, and explaining the intricacies of the lookup rules is complicated, even for language designers talking to other language designers.
This would hit Linq hard in particular, as `System.Linq` is on the shortlist of default global usings, but users often add their own versions of the extension methods to
customize particular behaviors or performance characteristics for particular scenarios. In these scenarios, users would have to add a manual `using System.Linq;` at the top
of their files, which is the same problem as we'd be trying to address by having separate scopes in the first place.
We considered a few compromise positions as well, such as considering global usings to be in the same scope for extension method lookup, but different scopes for type lookup.
These are complex, both in terms of implementation and in terms of explaining to users, so we don't think this is a good approach. Instead, we think that we have a good tooling
story around name clashes already, and we can strengthen the tooling to be aware of global using clashes and introduce global aliases to solve the issue, which is a fairly simple
fix for the problem.
Finally, we noted that even if we introduced a separate scope for global usings, this wouldn't even fix the issue permanently. The next request would be a way to separate the
implicit usings from the SDK, and the user-created global usings.
#### Conclusion
Existing behavior is upheld.

View file

@ -0,0 +1,81 @@
# C# Language Design Meeting for July 26th, 2021
## Agenda
1. [Lambda conversion to System.Delegate](#lambda-conversion-to-system-delegate)
2. [Direct invocation of lambdas](#direct-invocation-of-lambdas)
3. [Speakable names for top-level statements](#speakable-names-for-top-level-statements)
## Quote of the Day
- "You just don't want me to have poor man expression blocks"
## Discussion
### Lambda conversion to System.Delegate
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md
We've received a few bug reports from customers on upgrading to a new version of the preview SDK that existing code, targeting .NET 5, was now failing to compile
because code that previously chose an extension method with a strongly-typed delegate type was now binding to an instance method that took a `Delegate` instead.
While this is a break we intended to make, we wanted to revisit it and make sure that we still believed it was appropriate for these cases. While we are still
waiting to hear back from these customers as to what their extension method was actually doing, a survey of code on GitHub shows that the vast majority of these
are what we expected: the extension forwards from a strongly-typed delegate to the `Delegate` instance method. Given this, we believe that our original decisions
around this feature are still what we were expecting, and that we will keep the behavior for C# 10.
However, we are also concerned with not making tooling updates that break our users (beyond bugfixes, such as the ternary type inference issues in C# 9's initial
release). This is code that has been compiling successfully for a decade or more, and we don't want to break it when the user has only updated their version of VS,
and not actually updated to a new version of .NET (ie, using VS 2022, but still targeting .NET 5 or lower). In order to preserve this, we will bring back our
workaround for the 16.10 release, where we changed the binding of this case depending on the current language version. It does mean that there will be a subpar
tooling experience for upgrades involving this type of code, but we think missing codefixes are better than code that completely stops compiling.
#### Conclusion
We will keep the current behavior for C# 10. C# 9 and lower will have a different binding behavior, compiling as they used to.
### Direct invocation of lambdas
https://github.com/dotnet/csharplang/issues/4748
Currently, the lambda improvements proposal states that lambdas should be directly invocable. While we think this could fall out for some scenarios, it will not do
so for all scenarios. For example:
```cs
(() => {})(); // Infer Action, can be invoked
(x => x)(1); // Cannot infer a delegate type on its own: would need to take the argument expression into account
```
We don't have a strong scenario for this feature at the moment: our best justification for the feature would be as a poor replacement for expression blocks, and we've
received vocal and vociferous feedback from the community on the "poor" part of that phrase. We do think it will be odd that lambdas will be able to be assigned to
`var` and then be invoked/used as instance receivers, but cannot be invoked/used as instance receivers directly, but we believe that if we explicitly block this scenario
now, we will adequately preserve our design space to remove this restriction later, if we have a better scenario we want to enable.
#### Conclusion
Lambda and method group natural types will only be allowed to surface in specific contexts: `var`, method type inference, determining the best type of a group of types,
and conversion to `System.Delegate`/`System.Linq.Expressions.Expression`. No direct invocation of lambdas or instance member access on lambda expressions or method groups
will be allowed.
### Speakable names for top-level statements
[Two weeks ago](LDM-2021-07-12.md#speakable-names-for-top-level-statements), we made a decision that top-level statements would be sugar for a partial `Program` type in
a `Main` method. However, a [comment on the subsequent discussion](https://github.com/dotnet/csharplang/discussions/4928#discussioncomment-1013469) gave us a separate
idea that we had not considered in the meeting, and we wanted to bring up the idea: naming the type that contains the top-level statements after the name of the file
the statements are in. There are a few wrinkles to this: what if the file name contains characters that are not legal as C# identifiers? Or what if there is no file name,
and the text was passed directly to the compiler not in a file (as is possible using the Roslyn APIs)? We're also concerned by the fragility of this solution: renaming a
file could potentially leave dangling partial types and no longer have methods in scope for the top-level statements. Additionally, C# today does not depend on file
layout or structure for programs: file names do not have to line up with type names, and namespaces are entirely unconnected from the physical layout of files. This
proposal would change that, and we're not sure for the better.
However, the idea that we might have a future where we want to combine multiple files, each with top-level statements, into a single program is a distinct possibility.
As we move towards the idea of `dotnet run Program.cs`, it's conceivable that a single program might have a few files for different UI stacks (for example, `WebUI.cs`,
`ConsoleUI.cs`, and `Desktop.cs`), each of which shares a bunch of common files in the same directory. Users would then choose which to run by just saying
`dotnet run Console.cs`, and all 3 of these UI entrypoint files would be combined into a single program, with the entrypoint selected by the file that was run. Given
that our specific ask for the speakability feature is being able to get access to the current Assembly (via `typeof(Program).Assembly`), we think that we can protect
future design space here by only making `Program` speakable, rather than both `Program` and `Main`. This will allow our future selves a space to design this multi-file
experience without worrying about having multiple methods named `Main` in the same type, as we will be able to combine these different partial `Program` types into a
single one and name the entrypoints whatever we need to.
#### Conclusion
We will keep the type name `Program` and make it speakable, but we will not make the `Main` method speakable.

View file

@ -0,0 +1,166 @@
# C# Language Design Meeting for August 23rd, 2021
## Agenda
1. [Nullability differences in partial type base clauses](#nullability-differences-in-partial-type-base-clauses)
2. [Top-level statements default type accessibility](#top-level-statements-default-type-accessibility)
3. [Lambda expression and method group type inference issues](#lambda-expression-and-method-group-type-inference-issues)
1. [Better function member now ambiguous in some cases](#better-function-member-now-ambiguous-in-some-cases)
2. [Conversions from method group to `object`](#conversions-from-method-group-to-object)
4. [Interpolated string betterness in older language versions](#interpolated-string-betterness-in-older-language-versions)
## Quote of the Day
- This is the thing that's not weird, so let's handle that
## Discussion
### Nullability differences in partial type base clauses
https://github.com/dotnet/csharplang/issues/5107
We looked at inconsistencies and errors when base and interface clauses differ by nullability today. This is particularly problematic
for source generators, as generated files are always `#nullable disable`d by default, and if the author just copied the class header
over it can cause a compilation error if the original declaration had type parameters and was `#nullable enable`d. The scenarios with
base types and interfaces are similar but not identical, as interfaces are allowed to be duplicated in metadata, but base types are not.
While the compiler is resilient to importing types with this duplicate metadata, we're not certain that relaxing anything with regards
to interface deduplication would be good for the whole ecosystem, as not every tool is written with the same level of error-recovery
as Roslyn is. We'll need to look more into the interface scenario before making any more changes.
For base types, the proposal suggests that we report warnings when there are differences between nullable and non-nullable type
parameters, but not for any other scenario. However, we think that this is a bit too big of an exception. As an example:
```cs
#nullable enable
public partial class Derived : Base<
object,
#nullable disable
object
#nullable enable
> {}
#nullable enable
public partial class Derived : Base<
#nullable disable
object,
#nullable enable
object
> {}
```
The type parameters only differ by obliviousness in this code, but we'd have to do a merge of all the declarations to get the "true"
nullability of all type parameters. We think this is an extremely complex case that gets away from the root of the problem: one
declaration is entirely oblivious. Therefore, a narrower, less complex compromise is to simply say that any entirely-oblivious base
type declarations are ignored when checking to see if the nullability of base types on all partial parts matches. This carves out the
simple case for source generators, and leaves the complex case for manual authors to get correct.
#### Conclusion
Entirely oblivious base type declarations are ignored when ensuring the nullability of all base type clauses in a partial type match.
### Top-level statements default type accessibility
ASP.NET has been testing the changes with [speakable types in top-level statements](https://github.com/dotnet/csharplang/blob/dcbaa815253df779d1ecc206c446c9eb6b059b82/meetings/2021/LDM-2021-07-26.md#speakable-names-for-top-level-statements),
and some initial feedback has been that, because the default for types in C# is internal, it requires `InternalsVisibleTo` for all test
projects. However, we don't think that this case is important enough to complicate the feature: making it `public` by default is different
than anything else in C#, and makes it a much more complex feature to specify, explain, and implement. We also think that the likelihood
is that most test projects are going to need IVT to their original at some point, so we wouldn't be saving much in the long run by making
`Program` public by default anyway.
#### Conclusion
Decision from the last meeting stands.
### Lambda expression and method group type inference issues
We looked at issues from https://github.com/dotnet/csharplang/issues/5095. The first item we determined to be an implementation bug before
the meeting and skipped it.
#### Better function member now ambiguous in some cases
Another entry in the continuing saga of breaking changes we're discovering from giving lambdas a natural type is a new way in which functions
that were previously unambiguous for lambda expressions are now ambiguous:
```cs
static void F(object obj, Action callback) { }
static void F(int i, Delegate callback) { }
// C#9: F(object, Action)
// C#10: ambiguous
F(0, () => { });
```
The reason for this is because, in C# 9, the `M(int, Delegate)` overload was not applicable at the call site, and only the `M(object, Action)`
was. However, with lambda expressions now having a natural type and being convertible to `Delegate`, we have 2 applicable overloads, with the
following conversions:
* `M(object, Action)`: implicit boxing numeric conversion from `int` to `object`, identity conversion from `Action` to `Action`.
* `M(int, Delegate)`: identity conversion from `int` to `int`, implicit reference conversion from `Action` to `Delegate`.
Neither of these sets of conversions is better than the other, so the method is now ambiguous. This was reported from ASP.NET, as they have
an unfortunate set of shipped methods that can hit this problem:
```cs
static class AppBuilderExtensions
{
public static IAppBuilder Map(this IAppBuilder app, PathSring path, Action<IAppBuilder> callback) => app;
}
static class RouteBuilderExtensions
{
public static IRouteBuilder Map(this IRouteBuilder routes, string path, Delegate callback) => routes;
}
```
where both `IAppBuilder` and `IRouteBuilder` are defined on `WebApp` instances. When a user calls `app.Map("sub", (IAppBuilder b) => {})`, this
is now ambiguous because each overload has 2 implicit non-identity conversions and 1 identity conversion, and the overloads are ambiguous.
We note, however, that these overloads are _already_ ambiguous if, instead of passing a method group or a lambda expression, the user instead
passes a variable of type `Action<IAppBuilder>`, with the same ambiguity. There is also a fairly easy workaround for ASP.NET, as they can add a
most-specific extension method of `Map(this IAppBuilder, string, Action<IAppBuilder>)`, which will have 2 identity conversions and 1 reference
conversion, making it more specific than the other two. We think this is a fine workaround, especially since this API is already ambiguous in
many cases, and will not be making any changes here.
##### Conclusion
No changes.
#### Conversions from method group to `object`
Allowing method groups to be convertible to `object` has a potentially deleterious effect on this simple scenario:
```cs
static object GetValue() => 0;
object o = GetValue;
```
In C# 9, the user would get an error stating that method groups cannot be converted to `object`. In C# 10, this is legal code and will compile.
However, we think the likelihood of this being intentional is much lower than the likelihood of it being a mistype. We have a few options for
addressing this:
- Require an explicit cast to object for such cases.
- Let an analyzer detect this case.
- Accept it as is.
- Have the compiler warn for this case.
We think that hard-requiring a cast to `object` is too prescriptive here, as this code is legal and well-formed. However, we also think that
either leaving it as is or leaving it to some other analyzer to detect this case is not ideal either. Given that, we think we should warn when
a method group is _implicitly_ converted to `object`. Suppressing the warning via an explicit cast or via a standard warning suppression
mechanism should be enough to ensure that there is clarity for these cases.
##### Conclusion
The compiler will warn on implicit method group conversions to `object`.
### Interpolated string betterness in older language versions
https://github.com/dotnet/roslyn/issues/55345
Finally today, we looked at a bug reported by a user when upgrading the compiler and target framework, but setting `LangVersion` back to 9 or
earlier. This causes an error for common calls such as `stringBuilder.Append($"")`, as they now pick the interpolated string handler overload
instead of the string overload, which then causes a language version error. We solved this similarly to how we dealt with lambda expressions,
where we made the better conversion code conditional on whether the language version supported interpolated string handlers. LDM had no issues
with this decision.
#### Conclusion
Decision upheld.

View file

@ -0,0 +1,47 @@
# C# Language Design Meeting for August 25th, 2021
## Agenda
1. [Interpolated string handler user-defined conversion recommendations](#interpolated-string-handler-user-defined-conversion-recommendations)
2. [Interpolated string handler additive expressions](#interpolated-string-handler-additive-expressions)
## Quote of the Day
- "Let's have a bio break until next Monday" [LDM proceeds to end an hour early]
## Discussion
### Interpolated string handler user-defined conversion recommendations
https://github.com/dotnet/csharplang/issues/5077
We think the scenarios such a recommendation would serve are extremely small, as we don't expect the vast majority of users to use handler types directly. Instead,
they will create interpolated string literals and the compiler will generate the handlers for the user. Additionally, while there's often not much protection, we're
concerned that some cases without `string` overloads for methods would cause forgetting a `$` to have unintended behavior, particularly when combined with other
operators such as `+`.
#### Conclusion
Recommendation rejected. We will not make strong statements on whether users should define implicit conversions from `string`.
### Interpolated string handler additive expressions
https://github.com/dotnet/csharplang/issues/5106
The root of this question is how we apply order of operations and the associative and commutative properties to parenthesized interpolated string addition expressions.
For example, given the following code: `$"...." + ($"...." + $"....")`, order of operations would suggest that we evaluate the components of part 1, then part 2, then
part 3. Then we combine parts 2 and 3, and finally combine the results of 1 with the results of 2+3. Interpolated string handlers can't operate in this "combine the
latter bits then prepend the former" mode. However, by the same token the side effects of this combination are mostly unobservable, and we're very leary of having
parentheses be ok in some scenarios but not in others. This means that we have 2 options:
1. Make parentheses always break the magic. A parenthesized interpolated string is just a string.
2. Make parentheses not matter. An expression composed only of additive expressions, interpolated string expressions, and parenthesized expressions counts as single
interpolated string for the purposes of handler conversions.
Option 1 forces us to try and answer awkward questions such as "What does `CustomHandler c = ($"" + $"");` mean?", and we don't like how it makes parentheses less
transparent than they are currently. Therefore, we'll go with option 2. As part of this, we'll also allow nullable suppression operators to be interspersed, as they
are similarly "transparent" and shouldn't have any impact on the final code.
#### Conclusion
Allow parentheses and nullable suppression operators anywhere in the interpolated string additive expression chain.

View file

@ -0,0 +1,118 @@
# C# Language Design Meeting for August 30th, 2021
## Agenda
1. C# 11 Initial Triage
1. [Generic attributes](#generic-attributes)
2. [`field` keyword](#field-keyword)
3. [List patterns](#list-patterns)
4. [Static abstracts in interfaces](#static-abstracts-in-interfaces)
5. [Declarations under `or` patterns](#declarations-under-or-patterns)
6. [Records and initialization](#records-and-initialization)
7. [Discriminated unions](#discriminated-unions)
8. [Params `Span<T>`](#params-spant)
9. [Statements as expressions](#statements-as-expressions)
10. [Expression trees](#expression-trees)
11. [Type system extensions](#type-system-extensions)
## Quote(s) of the Day
- "Any proposals for a prioritization strategy?"
- "I'm not going to run a ranked choice algorithm in real time"
- "By small, I mean large"
- "Even Midori wasn't crazy enough to put ref and out into the type system" "Maybe that's what went wrong"
- "If you're using ref fields for something other than performance, come talk to me and we can find you help"
## Discussion
Today, we start our _initial_ triaging passes for C# 11. It's important to note that, while we talked about a number of potential features for
this cycle, it's extremely unlikely we'll be able to get to all of them. These triage sessions are a good way to see what the LDM is currently
thinking about, but please don't try to speculate about what's in or out of C# 11 from them. We don't even know that yet.
### Generic Attributes
https://github.com/dotnet/csharplang/issues/124
We're shipping this in preview in C# 10 because we found a number of late-breaking incompatibilities with other tools. C++/CLI will straight-up
crash if it encounters an assembly with a generic attribute, even if the type with the attribute is entirely unused, and they're not the only
tool with issues. We'll need to work with them and others to make sure the ecosystem won't crash around generic attributes before we can remove
the preview flag.
### `field` keyword
https://github.com/dotnet/csharplang/issues/140
Didn't quite have time for this in 10. Let's get it done!
### List patterns
https://github.com/dotnet/csharplang/issues/3435
We have a syntactic design and semantic design for arrays and indexable types, but we will need to some more work for `IEnumerable` support.
We hope to have an initial preview soon into the C# 11 development cycle to help get user feedback on the design choices we've made so far.
### Static abstracts in interfaces
https://github.com/dotnet/csharplang/issues/4436
We'll need to look through the feedback consumers give us on the initial preview. We already know that CRTP (ie, `INumeric<T> where T : INumeric<T>`)
might be better served with `this` type constraint, and if we do want to have such a constraint we'll want it now so that the generic math
interfaces can use it where appropriate.
### Declarations under `or` patterns
https://github.com/dotnet/csharplang/issues/4018
This is one of our more common pattern complaints. Let's try and get this done.
### Records and initialization
We have a number of topics around both records and initialization:
1. Required properties
2. Final initializers
3. Factories
4. Primary constructors
5. Public init fields
6. Immutable collection initializers
7. Combined object and collection initializers
8. Nested properties in with-exprs
9. Event hookup in object initializers
We'll look at triaging this list in the next meeting.
### Discriminated unions
https://github.com/dotnet/csharplang/issues/113
There is a ton of interest in this, both within the LDM and in the wider community. At the same time, this is a potentially broad topic, broader
than records, and we have not yet done the work to fully explore the space and break things into individual parts that can combine into a bigger
whole like we did with records. We'll need to start that now if we ever want to ship something in this space (and we do).
### Params `Span<T>`
https://github.com/dotnet/csharplang/issues/1757
This was initially going to be part of the improved interpolated strings proposal, but that feature grew and pushed this part out of scope. We'll
need to work together with the runtime on this to make sure that we're stackalloc'ing where possible but not unnecessarily blowing out stacks. It
might also call for being able to stackalloc an array of reference types.
### Statements as expressions
https://github.com/dotnet/csharplang/issues/3086
We see potential for smaller features here that we can ship one at a time, leaving design priority for other features.
### Expression trees
https://github.com/dotnet/csharplang/discussions/4727
Historically, our inability to improve this space has been because of concern about breaking our customers. However, recent discussions between
EF and members of the LDT have us encouraged, and we plan to look at this.
### Type system extensions
There are a number of type system improvements we could look at, from large changes like roles and extension everything to smaller things such as
unifying our story around `Task` and `Task<T>`/`Action` and `Func`/etc. This last thing would require some kind of unit-type, and `void` is the
obvious choice as it's already the return type of nothing-returning methods. Maybe we could work with the BCL to unify these types and make them
transparent? We could also look at generic improvements around other restricted types, such as pointers and ref structs.

View file

@ -0,0 +1,85 @@
# C# Language Design Meeting for September 1st, 2021
## Agenda
1. [Lambda expression conversions to `Delegate`](#lambda-expression-conversions-to-delegate)
2. [C# 11 Initialization Triage](#c-11-initialization-triage)
1. [Required properties](#required-properties)
2. [Primary constructors](#primary-constructors)
3. [Immutable collection initializers](#immutable-collection-initializers)
## Quote(s) of the Day
- Starting the meeting with 11 minutes of technical issues
- "In my next language, no users"
- "Has anyone made that companion book? Javascript, The Terrible Bits"
- "Do we actually want to do this feature because customers are asking for it, or do we just think it's interesting as an LDM?"
## Discussion
### Lambda expression conversions to `Delegate`
https://github.com/dotnet/csharplang/issues/5124
Today, we do not allow lambdas without a natural type to be convertible to `Delegate` or `Expression`. This can lead to niche but possible
cases where _adding_ type information to a lambda actually makes a method call ambiguous, where the typeless lambda was unambiguous. These
cases, however, are both niche and usually fixable by adding a new, most-specific overload, as we discussed in the notes
[last week](LDM-2021-08-23.md#better-function-member-now-ambiguous-in-some-cases). Making a change here would also break APIs where the
user has an instance method that takes a `Delegate` (often not defined in their code) and adds extension methods with strongly-typed delegates
to add the necessary information for the compiler to bind their lambdas. This is a fairly common pattern when working with a `Delegate` API,
and despite making the language more complex and having weird consequences for niche cases, we think that keeping the existing rule as it is
the appropriate compromise to keep existing code working. While we don't think that we would have this complex of a rule if we were redesigning
the language, we also think that we've hit the limit of breaking changes we'd want to take in this feature.
#### Conclusion
We'll keep the conversion rules as they are.
### C# 11 Initialization Triage
Continuing from [last meeting](LDM-2021-08-30.md), we're triaging potential C# 11 features, this time specifically around initialization
and records. The same disclaimer from last time applies: this is _early_ triage. Please do not try to infer what will be in C# 11 or not from
these notes. If we don't know the answer to that, these notes won't help you find it either 🙂.
The general list of initialization and record related topics is as follows:
1. Required properties
2. Final initializers
3. Factories
4. Primary constructors
5. Public init fields
6. Immutable collection initializers
7. Combined object and collection initializers
8. Nested properties in with-exprs
9. Event hookup in object initializers
Of these, we can subdivide them into a few sets of categories:
* Truly new features that enable a new type of expressiveness that doesn't exist today. This is 1, 2, 3, and 6.
* Features that build off of others, either in this list or already in the language. This is 4 and 5.
* Features that solve pain points in existing features. This is 7, 8, and 9.
Of these, there are a few that stand out as being features we're more interested in: required properties, primary constructors, and immutable
collection initializers.
#### Required properties
https://github.com/dotnet/csharplang/issues/3630
We did a big design push on this a little under a year ago. We should revive the proposal, make the changes we talked about at the end of
the last design review, and see what we think about it now.
#### Primary constructors
https://github.com/dotnet/csharplang/issues/2691
We've had a few designs for primary constructors over the years. C# 6 nearly shipped with one design, then we shipped records with a design,
and now we need to think about how primary constructors will interact with record/class cross inheritance when we get to that. We really need
to come back with a new proposal that looks at the past versions, and that may well be next year.
#### Immutable collection initializers
This is an idea we've been ruminating on as we designed the syntax for list patterns: we've given up on trying to make the correspondence
principle work with the existing collection initializer syntax, but we could potentially make the principle work by design a new collection
literal syntax, one that will work with immutable types as the current one does not. A smaller group will look at this and make a proposal
for the space.

View file

@ -0,0 +1,87 @@
# C# Language Design Meeting for September 13th, 2021
## Agenda
1. [Feedback on static abstracts in interfaces](#feedback-on-static-abstracts-in-interfaces)
## Quote(s) of the Day
- "Computer math never equals real math, it never does"
- "If we add default interface methods to LINQ, we get dim sum."
## Discussion
https://github.com/dotnet/csharplang/issues/4436
Today, we looked at some of the initial feedback we've gotten on both static abstracts in interfaces and generic math from our blog posts and on our design issues.
Some of the channels we looked at:
* https://github.com/dotnet/csharplang/issues/4436
* https://github.com/dotnet/designs/pull/205
* https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/
* https://www.reddit.com/r/csharp/comments/p20xap/preview_features_in_net_6_generic_math/
* https://www.reddit.com/r/dotnet/comments/p20jlc/preview_features_in_net_6_generic_math/
* https://www.reddit.com/r/programming/comments/p20j22/preview_features_in_net_6_generic_math/
* https://twitter.com/tannergooding/status/1425223277941719040?s=20
* https://twitter.com/dotnet/status/1428127070530514944?s=20
We've also had discussion on our various other platforms not so easily directly linked, such as the C# community discord (https://discord.gg/csharp), email, and
other similar informal chats. Overall, we're pleased to see both the number of people looking at the feature, and that their reactions to the feature are overwhelmingly
positive. We also received a good deal of concrete feedback we can examine to see what, if anything, we should change or improve about the feature before shipping for
real with .NET 7.
### Keyword confusion
Some users didn't immediately connect the idea of `static` and `abstract`, and suggested that we might want to consider a new keyword specifically for this concept.
However, we think this is another case of initial reactions wanting new features to stand out. This is particularly exacerbated because `abstract` is not used in
interfaces today, but is required here. Despite that, we intend to keep the keywords the way we have them: even if we introduced something special for interfaces,
we'd want to use `static abstract` in classes when we support defining such methods there.
### Traits/Shapes/Roles
Some of the feedback has been about asking us to go further, into traits/shapes/roles/type expansion du jour. This is only natural: static abstracts represents a major
new expressive ability in C#, and these types of expansions are the logical next step. While the feature we're currently working is not going to address those requests,
we by no means are done in this space, and will continue to explore more enhancements we can make in this space.
### Missing types
We didn't get to implementing the generic math interfaces on all types that would benefit from them in .NET 6, such as `BigInteger` or `Vector2/3/4`. We plan to expand
our implementations on these interfaces with .NET 7 to cover more types.
### Direct parsing support in INumber
Some users have expressed a sentiment that proper mathematical numbers don't necessarily have serialization/deserialization as a concept, and thus asked for INumber to
not include those concepts in the extended interface set. This will be a question for the runtime API reviewers to think on.
### Self types
Of the biggest pieces of feedback we've gotten is around confusing on type constraints. In particular, users expect calls like `IParsable<double>.Parse("123", null)` to
work, but because of the way we've implemented the type constraints currently, the language cannot understand that `double` is the type that `Parse` needs to be constrained
to. We can solve issues like this by implementing a self type, which will also just be a generally-useful feature for the language. We're positive about the feature, but
it is going to need a decent amount of design work. In particular, there are interfaces such as `IEquatable<T>` that have been in C# since C# 2.0, and we need to think about
if we want to be able to apply such a constraint to those interfaces, how that would work, and what level of breaking change it would be. There is also syntax to be worked
out, which makes for everyone's favorite LDMs.
### DIMs for static abstracts
We consider the ability to have a DIM for static virtual members a must-have for v1, as we'll need it for versioning, and potentially some of the initial implementation will
want to be a DIM instead of being required to be implemented by users.
### `static abstract`s in classes
The ability to declare `static abstract` members in classes will almost certainly be in the runtime for .NET 7. However, we don't consider it as important of a ship-blocker
as the DIM feature, so it might have less priority than other C# 11 features.
### Checked operators
https://github.com/dotnet/csharplang/issues/4665
We think this operator is likely important for semantic correctness in generic math. It's a niche case, and we may want to use the afformentioned DIMs to forward the checked
implementation to unchecked, but that will be a decision for the runtime API design team.
### Relaxing shift operator requirements.
https://github.com/dotnet/csharplang/issues/4666
Our thoughts on this proposal have not changed since the [last time](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-05-19.md#relaxing-shift-operator-requirements)
we looked at it.

View file

@ -0,0 +1,73 @@
# C# Language Design Meeting for September 15th, 2021
## Agenda
* [Feedback from the C# standardization committee](#feedback-from-the-c-standardization-committee)
* [Permit pattern variables under disjunctive patterns](#permit-pattern-variables-under-disjunctive-patterns)
## Quote of the Day
- "We've so far decided not to decide"
## Discussion
### Feedback from the C# standardization committee
https://github.com/dotnet/csharpstandard/issues/366
For the first half of today's LDM, we had a guest from the C# standardization committee on to talk about the ongoing process by TC-49
to produce an update to the ECMA C# specification, the latest version of which covers C# 5. In the csharplang repository, we currently
have an initial conversion of an internal Microsoft C# specification of C# 3, which was updated to cover some aspects of C# 6. The ECMA
team has converted the C# 5 specification to markdown, and is currently working to integrate the changes from our C# 6 spec into that
version.
These dual versions of the specification can confuse users. The language reference on docs.microsoft.com comes from the csharplang
version of the spec, but the TC-49 version of this specification has had a number of bug fixes and is a better markdown conversion overall.
Because of this the csharplang version of the spec gets occasional pull requests to update various things, from English spelling and
grammar issues to actual spec bugs, but many times those issues have already been fixed in the TC-49 version of the specification. To
address this, we plan to remove the csharplang version of the specification when the last PRs for C# 6 are merged into the csharpstandard
repo, which should hopefully be soon.
Additionally, the standardization committee has draft PRs out for most C# 7 features, and is working on C# 8 specifications as well.
Currently, when they run into questions they've been emailing a few specific compiler team members, who hopefully can either answer their
questions or forward to the right team member. To facilitate better interactions, we've created an internal alias with the compiler team,
the language design team, and the TC-49 members currently working on the draft specifications. The specification changes for C# 8 are big,
particularly with nullable reference types, and will require close collaboration between these groups to make sure the spec actually reflects
the feature that was implemented by the compiler team.
Finally, we did some thinking about how to collaborate moving forward on new language features. We'd like to think about maintaining proposals
as branches on the TC-49 specification, to make sure that we are considering the real specification when we think about new language features
and ensuring that we can see other proposed changes to the specification as a whole when making new features, as opposed to having to remember
what not-yet-specified proposal from a previous language version modified a particular section of the specification while designing a new
change to that section.
### Permit pattern variables under disjunctive patterns
https://github.com/dotnet/csharplang/issues/4018
We took a first pass over this proposal in the second half of the LDM. At first brush, there are a couple of major points of contention:
1. Should we allow redeclaration, or should we have some form of `into` pattern that would allow the reuse of an existing variable in
a pattern?
2. Is variable declaration across multiple expressions ok, or should it only be permissible within a single pattern?
Point 1 arises from potential confusion around the double declaration: will users find the multiple declarations intuitive, or will they
wonder about "which" of the pattern variables future usages refer to? We've also had requests for an `into` pattern in the past, that would
allow a pattern to assign into an existing variable. We're concerned about a generalized version of this pattern because it could have
unpredictable effects, particularly when combined with `when` clauses, but a more specialized version that can only use variables declared
in the same pattern could be a usable version of this proposal. We also want to think about how this would interact with any potential
pattern matching over types themselves in the future, such as extracting the `T` from an `IEnumerable<T>`: if such a thing could be done,
it should be doable under an `or` as well, and we will want to have similar syntax forms for redeclaration as here. We also need consider
how an `into` pattern or other form of assignment syntax would interact if we ever want to permit non-constants to be used as a pattern
themselves.
For point 2, we're concerned about the potential large impact throughout the language. This would be very similar to permitting a
generalized `into` pattern, where `when` clauses can cause otherwise-matching patterns to not be executed and reassign existing variables.
Several members of the LDM feel that we should tackle just within a single expression first, and consider multiple expressions at a later
time with more examples of the types of code it would enable.
#### Conclusions
No conclusions today. We want to see the specification PR updated with more motivating samples, including samples from real code, before
we make any conclusions on these issues.

View file

@ -0,0 +1,168 @@
# C# Language Design Meeting for September 20th, 2021
## Agenda
1. [Lambda breaking changes](#lambda-breaking-changes)
2. [Newlines in non-verbatim interpolated strings](#newlines-in-non-verbatim-interpolated-strings)
3. [Object initializer event hookup](#object-initializer-event-hookup)
4. [Type alias improvements](#type-alias-improvements)
## Quote of the Day
- "You're kicking in an open door"
## Discussion
### Lambda breaking changes
https://github.com/dotnet/roslyn/pull/56341
In the ever continuing saga of new breaking changes introduced by giving method groups and lambda expressions natural types, we looked
at a few new breaking changes today to decide what, if any, workarounds we should adopt to try and fix them.
#### Breaking changes around overload resolution
https://github.com/dotnet/roslyn/issues/55691
https://github.com/dotnet/roslyn/issues/56167
https://github.com/dotnet/roslyn/issues/56319
https://github.com/dotnet/csharplang/discussions/5157
We have a number of reports from users who have been broken by changes in overload resolution, mostly because a set of overloads that
used to succeed in overload resolution are now ambiguous. A smaller group met to discuss a number of different potential solutions to
the issue. These options were:
1. Leave the breaking changes as-is.
2. Change “method type inference” and “best common type” to not infer from the natural type of a lambda expression or method group.
3. Change “better function member” to treat delegate types with identical signatures as equivalent, allowing tie-breaking rules to apply.
4. Change “better function member” to prefer overloads where method type inference did not infer type arguments from the natural types
of lambdas or method groups.
5. Change “better function member” to prefer parameter types that are delegate types other than those used for natural type. (Prefer `D`
over `Action`.)
6. Change “better function member” to prefer argument conversions other than “function type” conversions.
7. Change “better function member” to prefer parameter types `D` or `Expression<D>` over `Delegate` or `Expression`, where `D` is a delegate type.
Discussion further narrowed our focus to two combinations of the above options: 3+7 or 4+6. 3+7 results in a more aggressive break, while
4+6 is more compatible with previous versions of C#. Given the extent of some of the breaks we're seeing, we think the more compatible
approach is the better way to go, so we'll proceed with the PR linked at the start of this section.
##### Conclusion
Options 4+6 accepted.
#### Method groups converting to `Expression`
Another break testing has revealed looks like this:
```cs
var c = new C();
c.M(F); // C#9: E.M(); C#10: error CS0428: Cannot convert method group 'F' to 'Expression'.
static int F() => 0;
class C
{
public void M(Expression e) { Console.WriteLine("C.M"); }
}
static class E
{
public static void M(this object o, Func<int> a) { Console.WriteLine("E.M"); }
}
```
We think we would have a solution for this: split our "function type conversion" into two separate conversion types: a function type
conversion from lambda, and a function type conversion from method group. Only the former would have a conversion to Expression. This
would make it so that `M(Expression)` is not applicable if the user passed a method group, leaving only `M(object, Func<int>)`. This
could be a bit complex, but it should resolve the issue.
Unlike the previous examples, however, we don't have any reports of this issue. Given the number of reports of the previous breakages
we've received, and the lack of reports for this issue, we tentatively think that it's not worth fixing currently. If, after we ship
C# 10 for real, we received reports of this break, we know how to fix it and can change course at that time without making a breaking
change.
##### Conclusion
No changes will be made.
#### Lambdas in OHI
A final break we looked at today is:
```cs
using System;
B.F1(() => 1); // C#9: A.F1(); C#10: B.F1()
var b = new B();
b.F2(() => 2); // C#9: A.F2(); C#10: B.F2()
class A
{
public static void F1(Func<int> f) { }
public void F2(Func<int> f) { }
}
class B : A
{
public static void F1(Delegate d) { }
public void F2(Delegate d) { }
}
```
This is standard OHI behavior in C#, but because the derived overloads were previously not applicable, they were not included in the
`B.F1` or `b.F2` method groups, and only the methods from `A` would be applicable. Now that methods from the more derived type are
applicable, methods from the base type are filtered out by method group resolution.
We think this is both fine and actually desirable behavior. We don't have contravariant parameters in C#, but this is effectively
acting like such, which is a good thing. This change is also not customer-reported, but was instead discovered in testing. Given the
desirable behavior and lack of reports, we think no change is necessary.
##### Conclusion
No changes.
### Newlines in non-verbatim interpolated strings
https://github.com/dotnet/csharplang/issues/4935
We have a lot of compiler complexity around ensuring interpolated strings do not have a newline in them, and we don't see a real
reason to forbid newlines. We think the origin might have come from the number of different design flip-flops we made on interpolated
strings during their initial design.
#### Conclusion
Language change approved.
### Object initializer event hookup
https://github.com/dotnet/csharplang/issues/5176
LDM is not only interested in this change, we're also interested in generalized improvements that can be made in object initializers
and with expressions. Compound assignment is interesting, particularly in `with` expressions, and we would like to see what improvements
we could make not just for events, but for all types of properties and fields.
#### Conclusion
Approved. We want to explore even more enhancements in this space.
### Type alias improvements
https://github.com/dotnet/csharplang/issues/4284
Finally today, we looked at one of the open questions in this proposal: how should we handle nullable types in using aliases when the
alias is used in a `#nullable disable` location.
There are largely 2 ways to view using aliases:
1. Syntactic substitutions: the compiler is literally copy/pasting the thing in the alias into the target location. In this view, the
compiler should treat the syntax as occuring at the use point, and warn based on that.
2. Semantic substitutions: the using alias is effectively defining a new type. It's not a truly different type, but only the meaning
is substituted, not the actual syntax. If we ever want to consider a way to export using aliases, this will be a useful meaning to assume.
We also have some (possibly unintended) prior art here: `using MyList = System.Collections.Generic.List<string?>;` takes the second
approach today, acting like a semantic substitution.
The one thing we still want to consider in this space is top-level nullability. We're not sure about allowing a type alias to have
top-level nullability when it's an alias to a reference type. There is (very intentionally) no extra C# syntax for "not null reference
type" beyond the lack of a `?`, and the next ask if we were to allow aliases to be top-level nullable would be for such a syntax.
#### Conclusion
Overall, we like the semantic meaning. We still need to consider whether aliases should be allowed to have top-level nullability.

View file

@ -0,0 +1,136 @@
# C# Language Design Meeting for September 22nd, 2021
## Agenda
1. [Open questions in list patterns](#open-questions-in-list-patterns)
1. [Breaking change confirmation](#breaking-change-confirmation)
2. [Positional patterns on ITuple](#positional-patterns-on-ITuple)
3. [Slicing rules](#slicing-rules)
4. [Slice syntax recommendations](#slice-syntax-recommendations)
5. [Other list pattern features](#other-list-pattern-features)
2. [Nested members in `with` and object creation](#nested-members-in-with-and-object-creation)
3. [CallerIdentityAttribute](#calleridentityattribute)
4. [Attributes on `Main` for top level programs](#attributes-on-main-for-top-level-programs)
## Quote of the Day
- "That's not just our normal passive aggressive shtick, it's just actively aggressive."
## Discussion
### Open questions in list patterns
https://github.com/dotnet/csharplang/issues/3435
https://github.com/dotnet/csharplang/issues/5201
#### Breaking change confirmation
First, we looked at the breaking change for `Length`/`Count` on indexable and countable types. We're a bit concerned about making this
case a generalized error, even when a list pattern isn't being used: there are potential use cases for a `Length` that returns negative,
such as some object modeling a physics concept. We think we'd like to try and downgrade this to a warning instead of an error, for the
specific case where a negative length is tested and the location wasn't already subsumed. Some examples:
```cs
x is { Length: -1 } // Warning, not subsumbed by any previous case
x is { Length: >=0 } // Considered Exhaustive
x is { Length: <5 or -1 } // Error on the -1, subsumed by <5. No warning on the <5.
```
##### Conclusion
Try to make it a warning for non-subsumed cases.
#### Positional patterns on ITuple
Currently, we've implemented subsumption rules that allows list patterns and `ITuple` positional patterns to understand they refer to
the same alias. However, we think that this results in weird interactions with regular `ValueTuple` instances. `ValueTuple`s explicit
implement `ITuple`'s properties, so when pattern matching on one they would not be considered indexable or countable unless boxed into
`ITuple`. This would mean that, unless we were to make `ValueTuple` special in list patterns, there wouldn't be the same correspondence
as `ITuple` has. We also don't think that, in general, `Deconstruct` implies an order for an indexer: other than for `ITuple`, these
seem to be unrelated concepts.
##### Conclusion
There will be no correspondence between positional patterns and list patterns, even for ITuple. If someone comes along with a killer
scenario for ITuple later, we can readd this.
#### Slicing rules
These rules, as stated, matches our previous discussions on the topic.
##### Conclusion
No changes.
#### Slice syntax recommendations
We have two general choices for the slice pattern:
```cs
e is [ first, .. var rest ] // Space
e is [ first, ..var rest ] // No space
```
We feel that the code reads oddly without the leading space because the pattern being applied itself has a space. It introduces an odd
feeling of `(..var) rest`, even though the code is really `.. (var rest)`.
##### Conclusion
We're in favor of having a space.
#### Other list pattern features
For `IEnumerable` patterns, we'd like to introduce them at the same time as the rest of the list pattern feature, even if in a more
limited form, to ensure we avoid any potential breaking changes. They may go to preview at different times, and we may need to section
off some of the pattern features to ensure we can ship, but we'd like to at least have an initial version. We also do want to make
sure that we're generating as good code for this as possible, which may overlap some with params Span work to see whether we can
stackalloc.
For indexer patterns, we are interested, but they don't need to be this version of C#. We do think that we'll want them in the near
future though: we have lists, we should also be able to have dictionaries.
For length patterns, we're not seeing the need. We explored them previously and the exploration seems to have concluded that they might
be needed for IEnumerable, but if so we can revisit as we look at IEnumerable again.
### Nested members in `with` and object creation
https://github.com/dotnet/csharplang/issues/4587
We like the concept of reducing the boilerplate for nested with patterns. However, we're not a fan of the strawman syntax. Multiple members
of the LDM misinterpreted it as modifying the nested instance, rather than doing a `with` on the nested property. It would also fall over
if multiple properties on a nested instance need to be set. We have a couple of initial ideas:
```cs
methodCallExpression with { Method with { Name = "MethodName", BaseType = "C" } }
methodCallExpression with { Method = { Name = "MethodName", BaseType = "C" } } // more similar to nested object initializer?
```
#### Conclusion
Into the working set, with some syntax modifications.
### CallerIdentityAttribute
https://github.com/dotnet/csharplang/issues/4984
Mostly a convenience feature for ASP.NET which will help them structure code in a way more conducive to AOT. It seems in line with our
existing caller info attributes, so we'll work with the BCL and ASP.NET teams to prioritize the issue.
#### Conclusion
Into the working set.
### Attributes on `Main` for top level programs
https://github.com/dotnet/csharplang/issues/5045
We do think there needs to be some design work: the feature proposes allowing the `main` modifier in any file. We need to decide whether
that's a use case we want to support, and if we do, whether we should allow the target with regular entry points or only with top level
statements. If we allowed it with any entry point, we think there are some decent engineering challenges with the way the language is
currently structured that would make it more complex than first blush might indicate. Some form of this feature seems reasonable though,
so we'll work with the BCL to determine the right scope of the feature.
#### Conclusion
Into the working set.

View file

@ -0,0 +1,97 @@
# C# Language Design Meeting for October 13th, 2021
## Agenda
1. [Revisiting DoesNotReturn](#revisiting-doesnotreturn)
2. [Warning on lowercase type names](#warning-on-lowercase-type-names)
3. [Length pattern backcompat](#length-pattern-backcompat)
## Quote of the Day
- "You're opinionated against opinionated features" "I see the irony, but..."
## Discussion
### Revisiting DoesNotReturn
https://github.com/dotnet/csharplang/issues/5231
We wanted to revisit this design decision after a few years and some reports of customer confusion around this attribute.
While it's not an overwhelming amount of confusion, it's non-zero. When last this attribute was
[discussed](../2019/LDM-2019-07-10.md#doesnotreturn), we decided that we couldn't use it to broadly enforce the concept of
a method that does not return: instead, it could only influence nullable analysis. Much like the rest of the features
nullable analysis added, it cannot make strong guarantees. Changing this behavior now wouldn't really solve any of the
issues with it, as code exists today that is attributed with `DoesNotReturn` that cannot be statically-proven to never
return.
Instead, we think that https://github.com/dotnet/csharplang/issues/538 would be needed to make progress here: once we have
a `never` type, these guarantees can be statically proven, the runtime can abort if an instance of `never` is ever created,
and we can introduce a warning wave at that point to suggest that methods attributed with `DoesNotReturn` should have a
return type of `never`. This would prevent us from introducing another loose generalized analysis that can't be statically
relied upon before later adding a more strict version that can be relied upon.
#### Conclusion
No changes.
### Warning on lowercase type names
https://github.com/dotnet/roslyn/issues/56653
This is an issue that we've been kicking around for a long time to try and protect language design space, and we've recently
started to make more breaks in this space. For example, C# 9 introduced `record` as a type specifier, and made it illegal
to declare a type named `record` without escaping it. We've heard no complaints about this change, and this has given us
more confidence to go ahead with a warning in this area.
And important point is that this change is _not_ motivated by style. C# as a language tries hard not to be opinionated on
coding styles; we have defaults for formatting that are part of VS and `dotnet new editorconfig`, but these are highly
customizable and we try not to punish users for taking advantage of that customizability. Instead, this warning is about
trying to ensure that C# has design space to work with that doesn't break users who upgrade to new C# versions. This goal
means we're trying to help 2 sets of customers:
1. People who author types with lowercase names. These authors should be informed that they could be causing issues for their
users by using a lowercase name.
2. People who use types with lowercase names. These users should be informed that, if a future version of C# adds the type
they're using as a keyword, their code could change meaning or no longer compile.
Some other points of consideration we brought up:
* Should we do this for just public types?
* Answer: we are trying to protect users from both themselves and others. This means we want to warn everywhere.
* Should we have codefixers to prepend `@` in front of identifiers?
* Ultimately this will be up to the IDE team, but we think that each set of customers above should have a different answer
here. For set 1, we don't want to encourage this type of naming, even when escaped, so we would not want to see a fixer on
type definitions. However, for set 2, they're just using a type someone else defined. We don't want to unreasonably punish
them, so a fixer to prepend `@` for these users seems appropriate.
#### Conclusion
The compiler will issue a diagnostic for types named all lowercase letters. We may simplify that to be just types named all
ASCII lowercase characters, to avoid worrying about lowercase characters in other encodings and because we don't believe C#
will be interested in adding non-ASCII keywords.
### Length pattern backcompat
https://github.com/dotnet/csharplang/issues/5226
There are user tradeoffs here. In our previous discussion, we expressed a desire to have a similar ability to nullable, whereby
a missing negative Length test would not count against exhaustive matching, but if one was present it would then cause negative
values to need to be fully matched against. However, describing these rules and implementing them is quite complex, to the point
that we are concerned both about the code complexity and the explanation of the rules to users. We therefore think that the
simpler implementation, where we just consider the domain of `Count`/`Length` for indexable and countable types to be non-negative
integers, to be the better approach.
While we were discussing this, we also brought up the inherent flaw of structural typing, where types that do not satisfy the contract
of indexable and countable but still have the correct shape end up having incorrect behavior. For example, the BCL recently approved
a `Vector2<T>` type. That type has an indexer, and it has a property called `Length` that returns an integer. This `Length` property
is not the length of the Vector: that's always 2. Instead, this is the euclidean length of the vector, ie the distance from (0, 0).
Our knowledge of `Length` and combining subsumption with list patterns will behave incorrectly here. However, we note that this type
is _already_ broken with structural typing in C# today. For example, `vector[^1]` would work on the type, but it wouldn't have the
expected behavior. Instead of trying to solve something here, we instead think we should see what other uses customers have for opting
out of structural typing, and started a discussion about that [here](https://github.com/dotnet/csharplang/discussions/5278).
#### Conclusion
We will accept the existing breaking change on subsumption for indexable and countable types, and treat Length/Count on types that are
both indexable and countable as if they can _only_ have non-negative values. We would like to get this change in preview sooner
rather than later so that we can receive feedback on the change.

View file

@ -0,0 +1,93 @@
# C# Language Design Meeting for October 20th, 2021
## Agenda
1. [Open questions in list patterns](#open-questions-in-list-patterns)
1. [Types that define both Length and Count](#types-that-define-both-length-and-count)
2. [Slices that return null](#slices-that-return-null)
2. [Primary constructors](#primary-constructors)
## Quote of the Day
- "No fingerprints today, I always struggle with it after a bank robbery, when I file them down"
## Discussion
### Open questions in list patterns
https://github.com/dotnet/csharplang/issues/5137
We had 3 open questions from tests on list patterns. Questions 1 and 2 both relate to types with both `Length` and `Count` properties, so our decision
applies to both.
#### Types that define both Length and Count
Our previous definition of a type that can be matched by a list pattern is that the type must be both indexable and countable. Because countable could be
one of two properties, `Length` or `Count`, we need to decide what do when a type has both. Our options are:
1. Treat them as equivalent and assume they return the same value. This will affect subsumption.
2. Treat the non-recognized one (`Count` in the case where both `Length` and `Count` are defined) as not being special at all.
Conceptually, we think it would be odd to assume that `Count` is equal to `Length`, even though we'll never use it for any form of slicing or length checking.
While it might be reasonable to assume they return the same value, we don't think it's an important scenario. We'll have nearly a year of preview on this
feature, so we have plenty of time to react to feedback if dogfooding shows that it's an important scenario.
##### Conclusion
We will not treat `Length` and `Count` as being connected when both are defined.
#### Slices that return null
We believe that well-defined slice methods should never return `null` for an in-bounds range, and we'll never generate a call to such a slice method with
an out-of-bounds range. However, we also don't think there's a customer for this scenario: a search of GitHub showed no `Slice` methods that return an
annotated value, and treating a slice method differently than its annotation would require both implementation effort and customer education. Given that
there are no known cases this affects, we don't think it's worth it. Similarly to the first question, if such scenarios are identified during preview, we
can correct appropriately.
##### Conclusion
If a `Slice` method is annotated, we will treat it as potentially returning `null` for the purposes of subsumption and exhaustiveness.
### Primary constructors
https://github.com/dotnet/csharplang/issues/2691
Now that records have been out for a year and we've expanded them to struct and reference types, we think it's time to start looking at primary constructors
again. We updated the proposal with the new syntax forms from our records work, removing or modifying components where they make sense. Unlike for records,
primary constructors aren't about defining the members that make up a grouping of data: instead, they're purely for defining the inputs to a class. So things
like deconstruction, equality, and public properties aren't automatically generated from the parameters. Instead, they become fields, that are then eliminted if
the field is never used outside of a field initializer.
We think there are a couple of open discussion topics:
1. Should the fields be readonly or not?
2. How important are primary constructor bodies for a generalized feature?
For question 1, we think that, for better or for worse, C# is a mutable-by-default language. We were able to change the defaults for `record` types because
mutability in a value-based reference type is actively harmful, but we have to consider a number of naming and mutability issues we were able to skirt around
in record types. Field naming conventions, for example, are heavily split in C#, with some users using a leading `_`, and some preferring to just use pascalCase
with no leading modifiers. We think `readonly` is similar: if users would like to modify the defaults of C#, they can define their own field and do so. If this
proves to be the wrong default we can make a change before it ships for real, or potentially invest in https://github.com/dotnet/csharplang/issues/188 to allow
putting modifiers on parameters.
For question 2, we think that it will be important, but that once https://github.com/dotnet/csharplang/issues/2145 is in the language, a good portion of
initialization code will be expressable in just a single initialization expression. It will miss some more complex scenarios, but we think just primary constructors
are a good first step.
In general, we also want to make sure we're defining the difference between primary constructors and required properties well. With the potential for multiple new
initialization features to ship in a single language version, we want to make sure they complement each other well. We think required properties work well for
public contracts: DTOs, POCOs, and other similar, nominal data structures that will expose these properties externally. Primary constructors, on the other hand,
are for other types, that do not define public surface area based on their parameters. Instead, they simply define input parameters, and can assign them to private
fields or otherwise manipulate them as they choose.
Some concrete feedback on the proposal:
* The exception for calling `this` for copy constructors feels premature, as they don't mean anything to any type except record types. If we generalize `with` in
the future, then the exception will make sense, and we can add it then.
* We should disallow methods with the same name as primary constructor parameters. This is confusing and while it could potentially be understandable code if the
parameter was never captured, could then have spooky action at a distance if the parameter was accidentally captured when a user forgot to write the `()` of the
method name.
#### Conclusion
These words are accepted.

View file

@ -0,0 +1,104 @@
# C# Language Design Meeting for October 25th, 2021
## Agenda
1. [Required members](#required-members)
2. [Delegate type argument improvements](#delegate-type-argument-improvements)
## Quote of the Day
- "It's officially late, not casually late"
## Discussion
### Required members
https://github.com/dotnet/csharplang/issues/3630
It's been nearly a year since we last looked at required members, so we started today by recaping the proposal as it currently
[exists](https://github.com/dotnet/csharplang/blob/ec01d26c47d8d3e3e10b1dd0ade96dae6f99934a/proposals/required-members.md). We also
took a look at the feedback we received when this was last shown to our design review team (mainly internal partners and former
language design members who aren't involved the day-to-day design of the language anymore). The feedback we received at that point
was that the proposal, as it currently stands, is too complex. We have a lot of syntax for enabling a set of contract modifications:
1. Users can say `init(Prop)` to signal that the constructor requires all things the type does, except the members in the `init` clause.
2. Users can say `init required` to signal that the constructor does not require any of the members the type does, and the compiler
will ensure that the user correctly initializes all members in that constructor.
3. Users can say `init required!` to signal the same 2, except that the compiler will not enforce that the user correctly initializes
all members in that constructor.
This syntax was quite specialized, as we intended it to be extensible enough that future work around factories would be able to take
advantage of the same syntax. However, upon coming back to the syntax, we have several people that changed their opinions on the form;
some of the people that didn't originally feel like the syntax "clicked" now like it, and others that originally liked it now think that
it doesn't work well. We took a look at the concrete cases that will want to use the above modifications:
* Copy constructors will want to opt out of the entire contract, as their job is to initialize all the fields to the original instance's
values.
* Types might have a grow-up story, where they may originally ship with just some number of required properties, but later may want to add
a convenience constructor that sets a few common properties. We think that this case, while not unreasonable, is not the common case for
this feature.
The first use case wants at least contract modification 3, and possibly 2 as well. It does not need 1. The second case needs contract
modification 1 to be expressible in C#. Given this, we think that there's a gradual approach we can take to introducing contract
modifications to the language. A potential first approach is just introducing modification 3, without any constructor body validation.
A later version of the language can, as part of a warning wave, turn on body validation, and then later allow individual member exceptions
as in modification 1. We are a bit split on this decision, however. We think a prototype to play around with that implements this initial
decision should help inform the initial feature set we want to ship for this feature. One thing we definitely don't want to do is ship
without constructor body validation and without feeling like we can make that the default state later via a warning wave, as that would
leave us in a bad position where validation is available, but off by default.
We also looked at different syntax forms, namely attributes. We'd considered attributes previously in the design, but steered away from
them to see what we could do with language-integrated syntax. We think we've gone as far down that road as we can, but our final design
still doesn't feel quite right. Given that, we think that having an attribute to express the contract modifications will serve us well.
It can be named more verbosely, should still be able to apply to factory methods, and required no new syntax for users to learn.
Finally, we also considered a couple of other questions:
* Should we have a new accessor, `req`, that is used for a required property instead of `init`?
* We think that this use case is too small, cuts out required setters, and cuts out the ability to mark fields as required.
* Should we use a warning or error on construction? Traditionally, "suppressions" correspond to warnings, and the contract modifications
can be viewed as suppressing the need to initialize things.
* They're not really suppressions though. It's changing the semantics of the code, so we think it's fine (and safer) for us to use
errors on construction, not warnings.
* We haven't considered how required members will interact with `default(StructType)`. Some thought needs to be put into that.
#### Conclusion
We will start working on an implementation of the simplified version of this proposal that uses attributes for contract modifications,
not specific syntax, and only has contract modification 3. Feedback will then inform whether we need 2 or 1 before we ship.
### Delegate type argument improvements
https://github.com/dotnet/csharplang/issues/5321
A couple of weeks ago, Microsoft held our annual internal hackathon, and one of the ideas that we worked on for the it this year was
relaxing type argument restrictions, specifically for delegate types. This would allow delegate types such as `Action<void>` or
`Action<in int, ref readonly Span<char>>`, where these types are restricted from use in delegate types today. In general, the LDM has
a lot of interest in this space, but there is some concern that this proposal doesn't go far enough in relaxing restrictions, and might
run into issues if we try to generalize it more fully. A very common pattern in generic code in C# is to further abstract over parameter
types: for example, a method might take a `Func<T, bool>` and a `T`, and pass the `T` parameter to the `Func<T, bool>` invocation. This
proposal falls apart for such methods, which is a very early cliff to fall over. If we can instead generalize this work, such that these
restricted types are allowed in more places, then we'd be in a much better position. Even if we don't ship the whole thing at once, we'd
really like to make sure we're not painting ourselves into a hole with a proposal like this.
As a part of this, we also want to explore a revamp to the .NET delegate story. Today, `Action` and `Func` are the de-facto delegate types
in C#: they're used throughout the BCL and lambdas/method groups use them where possible for their natural type. However, there are limits
to this, as custom delegate types still have some advantages:
* Ref kinds/restricted type arguments. This is solved by the current proposal as it stands.
* Parameter names. We don't think this would be very difficult to solve, as we have prior art with tuple names.
* Attributes. This is much more difficult to accomplish, to avoid needing to synthesize custom delegate types we'd need to have some way of
putting attributes on a type argument. The runtime does not support this today, and it won't be as simple to add as the type restriction
removals were.
We've been moving towards structural typing of delegates ever since `Action` and `Func` were introduced, and we think it might be a better
approach to the problem to start from fully structural delegate types, and work backwards to see how we could make that work. It's very
likely that we'd end up doing some of the same work generalizing on `Action` and `Func`, but there's some additional interesting wrinkles
around `Action` and `Func<void>`. We might be able to unify these two, but we then want to be able to do the same for other types. How
would we make it work for `Task` and `Task<void>`, for example? Is there some attribute we could add to the runtime that would allow it
to treat these types as identical, forwarding from one to the other? And how would that flow through the C# type system?
#### Conclusions
We don't have any conclusions today. We think that much more exploration of this space is needed before moving forward with any specific
proposal. We want to look into generalizing the generic restriction removals, and into generalized structural typing for delegates.

View file

@ -0,0 +1,79 @@
# C# Language Design Meeting for October 27th, 2021
## Agenda
1. [UTF-8 String Literals](#utf-8-string-literals)
2. [Readonly modifiers for primary constructors](#readonly-modifiers-for-primary-constructors)
## Quote of the Day
- "I fought for that when I was young and idealistic"
## Discussion
### UTF-8 String Literals
https://github.com/dotnet/csharplang/issues/184
UTF-8 strings are an extremely important part of modern day programming, particularly for the web. We've previously talked about including special support for literals
based on UTF-8 in C#, as currently all our literals are UTF-16. It is possible to manually obtain byte arrays of UTF-8 bytes, but the process for doing so is either:
1. Error-prone, if you're hand-encoding a byte array.
2. Cumbersome and slightly inefficient, if you're creating static data to be later used.
3. Very inefficient, if you're converting the bytes every invocation.
We'd like to address these issues, doing the minimum possible work to make current scenarios more palatable without blocking our future ability to innovate in this space.
Currently, the runtime does not have a real `Utf8String` type, and instead uses `byte[]`, `Span<byte>`, or `ReadOnlySpan<byte>` as the de-facto interchange type for UTF-8
data. Many members of the LDM are concerned that, if we bless these types with conversions in the language, we will limit our future ability to react to the addition of
such a type into the runtime itself. In particular, if `var myStr = "Hello world"u8;` meant any of those three types, we lose out on the ability to make it mean `Utf8String`
in a future where that is added. This issue is further compounded because we don't know that such a type _will_ be added in the future: ongoing discussions are still being
had over whether a dedicated type will be added, or if the runtime could just have a flag to opt into having all `System.String` instances just become UTF-8 under the hood.
The u8 suffix had mixed reception with LDM. On the one hand, turning strings into one of the interchange types (either implicitly via target-typing, or only via explicit
cast) is convenient from a programming perspective. It also doesn't create a blessed syntax form that runs into the problems of the previous paragraph. On the other hand,
there is no direct indication _how_ the strings are being converted to bytes, which the suffix is useful for. Are the bytes just a direct representation of the UTF-16 data,
or an encoding the string in UTF-8 bytes?
Another question is whether a language-specific conversion is the correct approach here, or if we could create a user-defined conversion from `string` to `byte[]`. This
would allow non-constants to be converted as well, and the language/compiler could make the conversion special such that it could be optimized to occur at compile-time
when constant data is involved. It does suffer from the same problems of what byte encoding is being used, however, and we have existing solutions for converting non-constant
strings to UTF-8.
We also discussed a few other questions:
* Should we have a u8 character literal?
* People cast `char`s to `byte`s in a number of places today, this would need a suffix.
* It would also still need to return a sequence of bytes, rather than a single byte, so what advantage would it bring over a single-character string literal?
* Should the byte sequence have a null terminator?
* Interop scenarios will want a null terminator. However, they can add one by including a `\0` at the end of the string, and an analyzer can catch these cases.
* On the other hand, most managed code scenarios would break if a terminator was included.
* How specific should we make the language around the compile-time encoding of a given string literal?
* We'd like to make it relatively loose, such that we can say "the compiler is free to implement the most efficient and allocation-free form possible."
#### Conclusion
We don't feel confident enough to make any final calls today, but we'd like to make a prototype with target-typed literals and get some usage feedback. We'll implement
that behind a feature flag, and give the compiler to our BCL and ASP.NET partners so they can give us their thoughts from real API usage.
### Readonly modifiers for primary constructors
https://github.com/dotnet/csharplang/discussions/5314
https://github.com/dotnet/csharplang/issues/188
We wanted to revisit this question after we received a large amount of feedback from the community after our [last meeting](./LDM-2021-10-20.md#primary-constructors) on
primary constructors. The feedback was notable in particular because of the volume, as our discussion issues don't normally generate as much discussion from as many separate
community members as this one did. The feedback generally tended in one direction: mutability is an ok default, especially given the history of C#. However, they would like
a succinct way to make the generated fields `readonly`, without having to fall back to manual field declaration and assignment. To address this, we took a look at another
longstanding C# proposal, allowing `readonly` on parameters and locals. Several LDM members are concerned about this as a general feature, mainly because of the "attractive
nuisance" issue. `readonly` is often a sensible default for locals and (in particular) parameters, but in practice LDM members do not find accidental local/parameter mutation
to be a real source of bugs. Meanwhile, if we introduce this feature in general, people will start to use it, and require usage where possible in their codebases. It can add
clarity of intent for reading later, but many members are not convinced it meets the bar with C# 20 years old.
However, scoping the feature down to just primary constructor parameters was slightly less controversial. Most members of the LDM are not opposed to the concept in general,
but we remain conflicted as to whether we need to have such a feature immediately. While we did get a good deal of immediate feedback, some LDM members are unsure whether
this feedback will persist after users actually get their hands on the feature and give it a try. We could also take primary constructors in a more radically different direction,
where we'd adopt a more F#-like approach and consider these parameters captures, not fields.
#### Conclusion
We did not come to any conclusions today. We will revisit soon to talk more about this area.

View file

@ -0,0 +1,73 @@
# C# Language Design Meeting for November 1st, 2021
## Agenda
1. [Order of evaluation for Index and Range](#order-of-evaluation-for-index-and-range)
2. [Collection literals](#collection-literals)
## Quote of the Day
- "So 'The customer wants it therefore it's wrong' is your new motto?"
## Discussion
### Order of evaluation for Index and Range
https://github.com/dotnet/roslyn/issues/57349
We looked at an inconsistency in the order of evaluation between `Index` and `Range` operations, when those are applied to a type that does not have
native methods that accept `Index` or `Range` types. The `Index` portion of the spec is fairly straightforward and we were easily able to determine
the compiler had a bug with the lowering it performed. However, the specification is unclear about the `Range` translation: it does not clearly
communicate the order of evaluation for the `receiver`, `Length`, and `expr` expressions that are cached. Our current behavior is inconsistent with
the `Index` behavior, and likely confusing to users. After a bit of discussion, we unanimously agreed to standardize the order of evaluation to be
consistent with the written original order in the source, followed by compiler-needed components if necessary. For indexers, this is `receiver`, then
`expr`, then `Length` if needed.
#### Conclusion
Codify the expected behavior as `receiver` then `expr` then `Length`.
### Collection literals
https://github.com/dotnet/csharplang/issues/5354
We took a first look at a proposal for a "collection literal" syntax in C#, doing a general overview of the proposed syntax form and gathering general
feedback on the proposal. We hope to approach this proposal as a "one syntax to rule them all" form, that can achieve correspondence with the list
pattern syntax form, support new types that collection initializers can't today (such as `ImmutableArray<T>`), and serve as a zero or even negative
cost abstraction around list creation that is as good or better than hand-written user code.
There's some unfortunate bad interaction with existing collection initializers, however. They'll still exist, and they will be advantageous in some
cases. Since the user is explicitly calling `new Type` on them, they have an obvious natural type, while collection literals will need some form of
target-type for cases where there is no obvious natural type or if the user is trying to select between ambiguous overloads. They also probably wouldn't
support this new splat operator, which also makes them worse in that case; even if we extended support for it, it's very likely that the new form would
lower to a more efficient form with the pre-length calculation that it able to support.
We're unsure about some of the various axes of flexibility in this space. Some of them that we talked about are:
* Existing collection initializers require the type to implement `IEnumerable` to be considered collection-like. Do we want to keep this restriction for
the new form? It would lead to an odd non-correspondence with list patterns, since they are pattern-based and not interface-based. The proposal actually
started more restrictive, but loosened to the current form after considering things like `HashSet<int> h = [1, 2, 3];` and deciding that was perfectly
fine.
* How specific do we want to make the lowering for various forms? Since it's a goal of the feature to be as optimal as possible here, if we prescribe
too specific of lowering forms then we potentially make it difficult to optimize the implementation. We know from experience, though, that while we'll
have a small window of opportunity just after ship to make changes to the way code is emitted, making changes years on will be dangerous and will likely
have impacts on some user's code.
* We think that a natural type for this syntax will have a lot of benefits, such as allowing it to be used for interfaces (`IEnumerable<int> ie = [1, 2];`)
and it will be in line with the recent work we did around lambda expressions. However, unlike lambdas, we don't have a single list type in .NET that is
unambiguously the natural type it should be. We'll need to dig into the pros and cons of the space and decide on how we want to approach this.
We also talked briefly about possible extensions of this space. One obvious one is dictionary literals. We think we can move ahead with collection
literals for now, as long as we keep both dictionary literals and dictionary patterns, to be sure we're holding ourselves to the correspondence principle.
Another area we should investigate is list comprehensions. We're not sure whether we want comprehensions, but we should at least look at the space and
make sure we're comfortable that we'd never consider them or leave ourselves the space to be able to consider them in the future.
Finally, we want to look at other contemporary languages and see what they do for this space. For example, F# has dedicated syntax forms for each of it's
main list/sequence types. Unlike C#, they have largely unified on a very few specific collection types. This allows them to have dedicated syntax for each
one, but this approach isn't likely to work well in C# because we have a much wider variety of commonly-used collections, tuned for performance across a
variety of situtations. Kotlin takes a different approach of having well-known methods to construct different collections, such as `listOf` or `arrayOf`.
It's possible that, with `params Span`, we'd be able to achieve 90% of what we're trying to do without needing to build anything into the language itself.
And then there are languages like Swift, Python, Scala, and others that actually have a dedicated literal for lists, with varying degrees of flexibility.
#### Conclusions
No conclusions today. A smaller group will begin a deep dive into the space to flesh out the various questions and components of this proposal, and come
back to LDM with more research in the area.

View file

@ -0,0 +1,80 @@
# C# Language Design Meeting for November 3rd, 2021
## Agenda
1. [Name shadowing in local functions](#name-shadowing-in-local-functions)
2. [`params Span<T>`](#params-spant)
## Quote(s) of the Day
- "params Span<T>. This is an easy one, we should be out in 10 minutes."
- "For some value of 8"
- "Stack goes up. Stack goes down. You can't explain that."
- "That killed my monologue." "Did you mean 'evil villain monologue'?" "No one killed me during the monologue
therefore I can't be an evil villain." "No one kills the evil villain during the monologue, they just learn the evil plan and then escape to thwart it when the villain
leaves the implementation to his minions."
- "Solved the mystery: cat sitting on the spacebar"
## Discussion
### Name shadowing in local functions
https://github.com/dotnet/csharplang/discussions/5327
In C# 8, we relaxed the name shadowing requirements for lambdas and local functions, but we wanted to revisit the specific implementation strategy and whether we went too
far. In particular, we allowed a local function to _both_ capture a variable from an outer scope and shadow that variable in a nested scope inside the local function. This
_and_ in the previous sentence is the part we're concerned with: several members of the LDT didn't realize that we were agreeing to this in the original discussions on
shadowing.
There are two general models of shadowing in C#:
* Locals shadowing another local within the same function. This is expressly disallowed, and has been since C# 1.
* Locals shadowing a field. This is allowed, but the user can always get to the field version by using `this`. In the same method, it is possible to use the field without
the `this` qualifier and shadow it in a nested scope.
Shadowing between local functions while also capturing that local from the outer scope has similarities to both of these models: on the one hand, it's a local variable,
even if that variable has been potentially lifted to a field in an implicit closure. On the other hand, it's a variable from a scope outside the current function, just like
a field. This duality leads to conflicting resolution strategies, but we ultimately think this has more in common with locals shadowing other locals in the same method than
it does with locals shadowing fields.
The rules we wish we had implemented are: if a lambda or local function (or a nested lambda or local function) captures a variable from an outer method, it is an error to
shadow that variable. However, these rules have been out in the wild for nearly 3 years at this point, and we don't feel that the level of concern warrants a language change
or a warning wave to cover it. If an analyzer wants to implement such a suggestion (in dotnet/roslyn-analyzers, for example), they are free to, but the compiler itself will
not do so.
#### Conclusion
We regret that simultaneous capture/shadowing was allowed, and now it's too late to really fix it. Future features should keep this regret in mind and not use the way shadowing
in local functions/lambdas works as precedent.
### `params Span<T>`
https://github.com/dotnet/csharplang/issues/1757
`params Span<T>` is a feature that has been requested since we first implemented `Span<T>` in C# 7, and has a number of benefits. Today, users interested in performance with
`params` methods need to maintain overloads for common scenarios, such as passing 1-4 arguments (as we do for `Console.WriteLine`). These APIs are often not straightforward
to implement: if there was a simple common method they could all delegate to, that API would have been exposed in the first place.
Because of this focus, `params Span<T>` is an interesting first for the language: a `Span<T>`-based API that is explicitly targetted at the average C# developer, rather than
at someone who already knows what a `Span<T>` and why they would want to use it. There are also strong conflicting desires in the space that makes it very difficult to design
a single lowering strategy. We want to avoid allocations where possible, but we also want to avoid blowing out the stack when large numbers of parameters are passed to a
`params` method. This is particularly important in recursive code: Roslyn regularly runs into issues when inlining decisions can affect whether we are able to compile some
customer's code, as the compiler is an extremely recursive codebase.
At the same time, we also want to avoid being overly specific in the language specification on how this feature works. We would like to view the details as more an implementation
concern, much like we do with how lambdas are emitted, and avoid making strong guarantees about what will or won't stackalloc for what scenarios. As this is more targetted to
perf than lambdas are, it's possible that we can't be as glib in this space as we can for lambdas, but it's an aspiration for the feature.
Despite our desire to be less specific, we do think it's important to the initial design, and therefore the conception of who the feature is for, to have an implementation
strategy in mind. There are a number of different strategies that have been brought, with various pros and cons:
* Using a custom struct with a specific layout, and then making a span from the start of that struct.
* A shadow-stack that exists solely for the purpose of being able to give out and return stackalloc'd chunks of memory (think RAII stack space).
* Pushing variables onto the stack, then making a span from the start of those pushes (similar to the first approach, but without a dedicated type to be wrapped).
* `stackalloc` space that can be popped after a function is called.
* Not doing any kind of optimized IL in C#, and relying on the JIT to perform the escape analysis and translation from array to stackalloc when viable.
#### Conclusion
We'd like to see a group from the compiler and the runtime work through these proposed strategies and come up with "the way" or combination of ways that this will work, so we
can start to see how the user experience for the feature would actually work.

View file

@ -1,36 +1,280 @@
# Upcoming meetings for 2021
All schedule items must have a public issue or checked in proposal that can be linked from the notes.
## Schedule ASAP
## Schedule when convenient
## Recurring topics
- *Triage championed features and milestones*
- *Design review*
## Apr 28, 2021
## Dec 15, 2021
## Apr 26, 2021
## Dec 1, 2021
## Apr 21, 2021
- Roles and extensions (Mads): https://github.com/dotnet/csharplang/issues/1711
- Record-structs multiple small clarifications (Julien): https://github.com/dotnet/roslyn/issues/51199
- Lambda improvements - method group inferred type breaking change (Chuck)
## Nov 10, 2021
## Apr 19, 2021
- Self-constraints (Tanner): https://github.com/dotnet/csharplang/pull/5387
- More open questions in interpolated strings (Fred, Stephen, Kathleen)
## Apr 14, 2021
# C# Language Design Notes for 2021
Overview of meetings and agendas for 2021
## Nov 3, 2021
[C# Language Design Notes for November 3rd, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-11-03.md)
1. Name shadowing in local functions
2. `params Span<T>`
## Nov 1, 2021
[C# Language Design Notes for November 1st, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-11-01.md)
1. Order of evaluation for Index and Range
2. Collection literals
## Oct 27, 2021
[C# Language Design Notes for October 27th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-10-27.md)
1. UTF-8 String Literals
2. Readonly modifiers for primary constructors
## Oct 25, 2021
[C# Language Design Notes for October 25th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-10-25.md)
1. Required members
2. Delegate type argument improvements
## Oct 20, 2021
[C# Language Design Notes for October 20th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-10-20.md)
1. Open questions in list patterns
1. Types that define both Length and Count
2. Slices that return null
2. Primary constructors
## Oct 13, 2021
[C# Language Design Notes for October 13th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-10-13.md)
1. Revisiting DoesNotReturn
2. Warning on lowercase type names
3. Length pattern backcompat
## Sep 22, 2021
[C# Language Design Notes for September 22nd, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-09-22.md)
1. Open questions in list patterns
1. Breaking change confirmation
2. Positional patterns on ITuple
3. Slicing rules
4. Slice syntax recommendations
5. Other list pattern features
2. Nested members in `with` and object creation
3. CallerIdentityAttribute
4. Attributes on `Main` for top level programs
## Sep 20, 2021
[C# Language Design Notes for September 20th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-09-20.md)
1. Lambda breaking changes
2. Newlines in non-verbatim interpolated strings
3. Object initializer event hookup
4. Type alias improvements
## Sep 15, 2021
[C# Language Design Notes for September 15th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-09-15.md)
* Feedback from the C# standardization committee
* Permit pattern variables under disjunctive patterns
## Sep 13, 2021
[C# Language Design Notes for September 13th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-09-13.md)
1. Feedback on static abstracts in interfaces
## Sep 1, 2021
[C# Language Design Notes for September 1st, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-09-01.md)
1. Lambda expression conversions to `Delegate`
2. C# 11 Initialization Triage
1. Required properties
2. Primary constructors
3. Immutable collection initializers
## Aug 30, 2021
[C# Language Design Notes for August 30th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-08-30.md)
1. C# 11 Initial Triage
1. Generic attributes
2. List patterns
3. Static abstracts in interfaces
4. Declarations under `or` patterns
5. Records and initialization
6. Discriminated unions
7. Params `Span<T>`
8. Statements as expressions
9. Expression trees
10. Type system extensions
## Aug 25, 2021
[C# Language Design Notes for August 25th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-08-25.md)
1. Interpolated string handler user-defined conversion recommendations
2. Interpolated string handler additive expressions
## Aug 23, 2021
[C# Language Design Notes for August 23rd, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-08-23.md)
1. Nullability differences in partial type base clauses
2. Top-level statements default type accessibility
3. Lambda expression and method group type inference issues
1. Better function member now ambiguous in some cases
2. Conversions from method group to `object`
4. Interpolated string betterness in older language versions
## Jul 26, 2021
[C# Language Design Notes for July 26th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-07-26.md)
1. Lambda conversion to System.Delegate
2. Direct invocation of lambdas
3. Speakable names for top-level statements
## Jul 19, 2021
[C# Language Design Notes for July 19th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-07-19.md)
1. Global using scoping revisited
## Jul 12, 2021
[C# Language Design Notes for July 12th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-07-12.md)
1. C# 10 Feature Status
2. Speakable names for top-level statements
## Jun 21, 2021
[C# Language Design Notes for June 21st, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-06-21.md)
1. Open questions for lambda return types
2. List patterns in recursive patterns
3. Open questions in async method builder
4. Email Decision: Duplicate global using warnings
## Jun 14, 2021
[C# Language Design Notes for June 14th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-06-14.md)
1. Open questions in CallerArgumentExpressionAttribute
2. List pattern syntax
## Jun 7, 2021
[C# Language Design Notes for June 7th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-06-07.md)
1. Runtime checks for parameterless struct constructors
2. List patterns
a. Exhaustiveness
b. Length pattern feedback
## Jun 2, 2021
[C# Language Design Notes for June 2nd, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-06-02.md)
1. Enhanced #line directives
2. Lambda return type parsing
3. Records with circular references
## May 26, 2021
[C# Language Design Notes for May 26th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-05-26.md)
1. Open questions in list patterns
## May 19, 2021
[C# Language Design Notes for May 19th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-05-19.md)
1. Triage
1. Checked operators
2. Relaxing shift operator requirements
3. Unsigned right shift operator
4. Opaque parameters
5. Column mapping directive
6. Only allow lexical keywords
7. Allow nullable types in declaration patterns
2. Protected interface methods
## May 17, 2021
[C# Language Design Notes for May 17th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-05-17.md)
1. Raw string literals
## May 12, 2021
[C# Language Design Notes for May 12th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-05-12.md)
1. Experimental attribute
2. Simple C# programs
## May 10, 2021
[C# Language Design Notes for May 10th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-05-10.md)
- Lambda improvements
## May 3, 2021
[C# Language Design Notes for May 3rd, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-05-03.md)
1. Improved interpolated strings
2. Open questions in record structs
## Apr 28, 2021
[C# Language Design Notes for April 28th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-28.md)
1. Open questions in record and parameterless structs
2. Improved interpolated strings
## Apr 21, 2021
[C# Language Design Notes for April 21st, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-21.md)
1. Inferred types for lambdas and method groups
2. Improved interpolated strings
## Apr 19, 2021
[C# Language Design Notes for April 19th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-19.md)
1. Improved interpolated strings
## Apr 14, 2021
[C# Language Design Notes for April 14th, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-04-14.md)
[C# Language Design Notes for April 14th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-14.md)
1. Shadowing in record types
2. `field` keyword
@ -38,34 +282,34 @@ Overview of meetings and agendas for 2021
## Apr 12, 2021
[C# Language Design Notes for April 12th, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-04-12.md)
[C# Language Design Notes for April 12th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-12.md)
1. List patterns
2. Lambda improvements
## Apr 7, 2021
[C# Language Design Notes for April 7th, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-04-07.md)
[C# Language Design Notes for April 7th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-07.md)
- MVP session
## Apr 5, 2021
[C# Language Design Notes for April 5th, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-04-05.md)
[C# Language Design Notes for April 5th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-05.md)
1. Interpolated string improvements
2. Abstract statics in interfaces
## Mar 29, 2021
[C# Language Design Notes for March 29th, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-03-29.md)
[C# Language Design Notes for March 29th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-29.md)
1. Parameterless struct constructors
2. AsyncMethodBuilder
## Mar 24, 2021
[C# Language Design Notes for March 24th, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-03-24.md)
[C# Language Design Notes for March 24th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-24.md)
1. Improved interpolated strings
2. `field` keyword
@ -76,14 +320,14 @@ Overview of meetings and agendas for 2021
## Mar 15, 2021
[C# Language Design Notes for March 15th, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-03-15.md)
[C# Language Design Notes for March 15th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-15.md)
1. Interpolated string improvements
2. Global usings
## Mar 10, 2021
[C# Language Design Notes for March 10th, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-03-10.md)
[C# Language Design Notes for March 10th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-10.md)
1. Property improvements
1. `field` keyword
@ -92,7 +336,7 @@ Overview of meetings and agendas for 2021
## Mar 3, 2021
[C# Language Design Notes for March 3rd, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-03-03.md)
[C# Language Design Notes for March 3rd, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-03.md)
1. Natural type for lambdas
1. Attributes
@ -102,7 +346,7 @@ Overview of meetings and agendas for 2021
## Mar 1, 2021
[C# Language Design Notes for March 1st, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-03-01.md)
[C# Language Design Notes for March 1st, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-01.md)
1. Async method builder override
2. Async exception filters
@ -110,20 +354,20 @@ Overview of meetings and agendas for 2021
## Feb 24, 2021
[C# Language Design Notes for February 24th, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-02-24.md)
[C# Language Design Notes for February 24th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-02-24.md)
1. Static abstract members in interfaces
## Feb 22, 2021
[C# Language Design Notes for February 22nd, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-02-22.md)
[C# Language Design Notes for February 22nd, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-02-22.md)
1. Global `using`s
2. `using` alias improvements
## Feb 10, 2021
[C# Language Design Notes for February 10th, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-02-10.md)
[C# Language Design Notes for February 10th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-02-10.md)
1. Follow up on record equality
2. Namespace directives in top-level programs
@ -139,7 +383,7 @@ Overview of meetings and agendas for 2021
## Feb 8, 2021
[C# Language Design Notes for February 8th, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-02-08.md)
[C# Language Design Notes for February 8th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-02-08.md)
1. Virtual statics in interfaces
1. Syntax Clashes
@ -149,14 +393,14 @@ Overview of meetings and agendas for 2021
## Feb 3, 2021
[C# Language Design Notes for February 3rd, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-02-03.md)
[C# Language Design Notes for February 3rd, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-02-03.md)
1. List patterns on `IEnumerable`
2. Global usings
## Jan 27, 2021
[C# Language Design Notes for January 27th, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-01-27.md)
[C# Language Design Notes for January 27th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-01-27.md)
1. Init-only access on conversion on `this`
2. Record structs
@ -168,19 +412,19 @@ Overview of meetings and agendas for 2021
## Jan 13, 2021
[C# Language Design Notes for January 13th, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-01-13.md)
[C# Language Design Notes for January 13th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-01-13.md)
- Global usings
- File-scoped namespaces
## Jan 11, 2021
[C# Language Design Notes for January 11th, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-01-11.md)
[C# Language Design Notes for January 11th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-01-11.md)
- Required properties simple form
## Jan 6, 2021
[C# Language Design Notes for January 5th, 2021](https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-01-05.md)
[C# Language Design Notes for January 5th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-01-05.md)
- File scoped namespaces

View file

@ -34,22 +34,22 @@ The scope of a *global_using_directive* specifically does not include *using_dir
The effect of adding a *global_using_directive* to a program can be thought of as the effect of adding a similar *using_directive* that resolves to the same target namespace or type to every compilation unit of the program. However, the target of a *global_using_directive* is resolved in context of the compilation unit that contains it.
# Scopes
## Scopes
https://github.com/dotnet/csharplang/blob/master/spec/basic-concepts.md#scopes
These are the relevant bullet points with proposed additions (which are **in bold**):
* The scope of name defined by an *extern_alias_directive* extends over the ***global_using_directive*s,** *using_directive*s, *global_attributes* and *namespace_member_declaration*s of its immediately containing compilation unit or namespace body. An *extern_alias_directive* does not contribute any new members to the underlying declaration space. In other words, an *extern_alias_directive* is not transitive, but, rather, affects only the compilation unit or namespace body in which it occurs.
* **The scope of a name defined or imported by a *global_using_directive* extends over the *global_attributes* and *namespace_member_declaration*s of all the *compilation_unit*s in the program.**
# Namespace and type names
## Namespace and type names
https://github.com/dotnet/csharplang/blob/master/spec/basic-concepts.md#namespace-and-type-names
Changes are made to the algorithm determining the meaning of a *namespace_or_type_name* as follows.
This is the relevant bullet point with proposed additions (which are **in bold**):
* If the *namespace_or_type_name* is of the form `I` or of the form `I<A1, ..., Ak>`:
* If `K` is zero and the *namespace_or_type_name* appears within a generic method declaration ([Methods](classes.md#methods)) and if that declaration includes a type parameter ([Type parameters](classes.md#type-parameters)) with name `I`, then the *namespace_or_type_name* refers to that type parameter.
* Otherwise, if the *namespace_or_type_name* appears within a type declaration, then for each instance type `T` ([The instance type](classes.md#the-instance-type)), starting with the instance type of that type declaration and continuing with the instance type of each enclosing class or struct declaration (if any):
* If `K` is zero and the *namespace_or_type_name* appears within a generic method declaration ([Methods](../../spec/classes.md#methods)) and if that declaration includes a type parameter ([Type parameters](../../spec/classes.md#type-parameters)) with name `I`, then the *namespace_or_type_name* refers to that type parameter.
* Otherwise, if the *namespace_or_type_name* appears within a type declaration, then for each instance type `T` ([The instance type](../../spec/classes.md#the-instance-type)), starting with the instance type of that type declaration and continuing with the instance type of each enclosing class or struct declaration (if any):
* If `K` is zero and the declaration of `T` includes a type parameter with name `I`, then the *namespace_or_type_name* refers to that type parameter.
* Otherwise, if the *namespace_or_type_name* appears within the body of the type declaration, and `T` or any of its base types contain a nested accessible type having name `I` and `K` type parameters, then the *namespace_or_type_name* refers to that type constructed with the given type arguments. If there is more than one such type, the type declared within the more derived type is selected. Note that non-type members (constants, fields, methods, properties, indexers, operators, instance constructors, destructors, and static constructors) and type members with a different number of type parameters are ignored when determining the meaning of the *namespace_or_type_name*.
* If the previous steps were unsuccessful then, for each namespace `N`, starting with the namespace in which the *namespace_or_type_name* occurs, continuing with each enclosing namespace (if any), and ending with the global namespace, the following steps are evaluated until an entity is located:
@ -65,7 +65,7 @@ This is the relevant bullet point with proposed additions (which are **in bold**
* Otherwise, if the namespaces and type declarations imported by the *using_namespace_directive*s and *using_alias_directive*s of the namespace declaration **and the namespaces and type declarations imported by the *global_using_namespace_directive*s and *global_using_static_directive*s of any namespace declaration for `N` in the program** contain more than one accessible type having name `I` and `K` type parameters, then the *namespace_or_type_name* is ambiguous and an error occurs.
* Otherwise, the *namespace_or_type_name* is undefined and a compile-time error occurs.
# Simple names
## Simple names
https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#simple-names
Changes are made to the *simple_name* evaluation rules as follows.
@ -83,7 +83,7 @@ This is the relevant bullet point with proposed additions (which are **in bold**
* Otherwise, if the namespaces and type declarations imported by the *using_namespace_directive*s and *using_static_directive*s of the namespace declaration **and the namespaces and type declarations imported by the *global_using_namespace_directive*s and *global_using_static_directive*s of any namespace declaration for `N` in the program** contain exactly one accessible type or non-extension static member having name `I` and `K` type parameters, then the *simple_name* refers to that type or member constructed with the given type arguments.
* Otherwise, if the namespaces and types imported by the *using_namespace_directive*s of the namespace declaration **and the namespaces and type declarations imported by the *global_using_namespace_directive*s and *global_using_static_directive*s of any namespace declaration for `N` in the program** contain more than one accessible type or non-extension-method static member having name `I` and `K` type parameters, then the *simple_name* is ambiguous and an error occurs.
# Extension method invocations
## Extension method invocations
https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#extension-method-invocations
Changes are made to the algorithm to find the best *type_name* `C` as follows.
@ -92,7 +92,7 @@ This is the relevant bullet point with proposed additions (which are **in bold**
* If the given namespace or compilation unit directly contains non-generic type declarations `Ci` with eligible extension methods `Mj`, then the set of those extension methods is the candidate set.
* If types `Ci` imported by *using_static_declarations* and directly declared in namespaces imported by *using_namespace_directive*s in the given namespace or compilation unit **and, if containing compilation unit is reached, imported by *global_using_static_declarations* and directly declared in namespaces imported by *global_using_namespace_directive*s in the program** directly contain eligible extension methods `Mj`, then the set of those extension methods is the candidate set.
# Compilation units
## Compilation units
https://github.com/dotnet/csharplang/blob/master/spec/namespaces.md#compilation-units
A *compilation_unit* defines the overall structure of a source file. A compilation unit consists of **zero or more *global_using_directive*s followed by** zero or more *using_directive*s followed by zero or more *global_attributes* followed by zero or more *namespace_member_declaration*s.
@ -107,17 +107,17 @@ A C# program consists of one or more compilation units, each contained in a sepa
The *global_using_directive*s of a compilation unit affect the *global_attributes* and *namespace_member_declaration*s of all compilation units in the program.
# Extern aliases
## Extern aliases
https://github.com/dotnet/csharplang/blob/master/spec/namespaces.md#extern-aliases
The scope of an *extern_alias_directive* extends over the ***global_using_directive*s,** *using_directive*s, *global_attributes* and *namespace_member_declaration*s of its immediately containing compilation unit or namespace body.
# Using alias directives
## Using alias directives
https://github.com/dotnet/csharplang/blob/main/spec/namespaces.md#using-alias-directives
The order in which *using_alias_directive*s are written has no significance, and resolution of the *namespace_or_type_name* referenced by a *using_alias_directive* is not affected by the *using_alias_directive* itself or by other *using_directive*s in the immediately containing compilation unit or namespace body, **and, if the *using_alias_directive* is immediately contained in a compilation unit, is not affected by the *global_using_directive*s in the program**. In other words, the *namespace_or_type_name* of a *using_alias_directive* is resolved as if the immediately containing compilation unit or namespace body had no *using_directive*s **and, if the *using_alias_directive* is immediately contained in a compilation unit, the program had no *global_using_directive*s**. A *using_alias_directive* may however be affected by *extern_alias_directive*s in the immediately containing compilation unit or namespace body.
# Global Using alias directives
## Global Using alias directives
A *global_using_alias_directive* introduces an identifier that serves as an alias for a namespace or type within the program.
@ -141,7 +141,7 @@ Accessing a namespace or type through an alias yields exactly the same result as
Using aliases can name a closed constructed type, but cannot name an unbound generic type declaration without supplying type arguments.
# Global Using namespace directives
## Global Using namespace directives
A *global_using_namespace_directive* imports the types contained in a namespace into the program, enabling the identifier of each type to be used without qualification.
@ -163,8 +163,7 @@ Furthermore, when more than one namespace or type imported by *global_using_name
The *namespace_name* referenced by a *global_using_namespace_directive* is resolved in the same way as the *namespace_or_type_name* referenced by a *global_using_alias_directive*. Thus, *global_using_namespace_directive*s in the same program do not affect each other and can be written in any order.
# Global Using static directives
## Global Using static directives
A *global_using_static_directive* imports the nested types and static members contained directly in a type declaration into the containing program, enabling the identifier of each member and type to be used without qualification.
@ -182,13 +181,13 @@ A *global_using_static_directive* only imports members and types declared direct
Ambiguities between multiple *global_using_namespace_directive*s and *global_using_static_directives* are discussed in the section for *global_using_namespace_directive*s (above).
# Namespace alias qualifiers
## Namespace alias qualifiers
https://github.com/dotnet/csharplang/blob/master/spec/namespaces.md#namespace-alias-qualifiers
Changes are made to the algorithm determining the meaning of a *qualified_alias_member* as follows.
This is the relevant bullet point with proposed additions (which are **in bold**):
* Otherwise, starting with the namespace declaration ([Namespace declarations](namespaces.md#namespace-declarations)) immediately containing the *qualified_alias_member* (if any), continuing with each enclosing namespace declaration (if any), and ending with the compilation unit containing the *qualified_alias_member*, the following steps are evaluated until an entity is located:
* Otherwise, starting with the namespace declaration ([Namespace declarations](../../spec/namespaces.md#namespace-declarations)) immediately containing the *qualified_alias_member* (if any), continuing with each enclosing namespace declaration (if any), and ending with the compilation unit containing the *qualified_alias_member*, the following steps are evaluated until an entity is located:
* If the namespace declaration or compilation unit contains a *using_alias_directive* that associates `N` with a type, **or, when a compilation unit is reached, the program contains a *global_using_alias_directive* that associates `N` with a type,** then the *qualified_alias_member* is undefined and a compile-time error occurs.
* Otherwise, if the namespace declaration or compilation unit contains an *extern_alias_directive* or *using_alias_directive* that associates `N` with a namespace, ***or, when a compilation unit is reached, the program contains a *global_using_alias_directive* that associates `N` with a namespace,** then:

View file

@ -1,10 +1,5 @@
# AsyncMethodBuilder override
* [x] Proposed
* [ ] Prototype: Not Started
* [ ] Implementation: Not Started
* [ ] Specification: Not Started
## Summary
[summary]: #summary
@ -12,7 +7,7 @@ Allow per-method override of the async method builder to use.
For some async methods we want to customize the invocation of `Builder.Create()` to use a different _builder type_.
```C#
[AsyncMethodBuilderAttribute(typeof(PoolingAsyncValueTaskMethodBuilder<int>))] // new usage of AsyncMethodBuilderAttribute type
[AsyncMethodBuilderAttribute(typeof(PoolingAsyncValueTaskMethodBuilder<>))] // new usage of AsyncMethodBuilderAttribute type
static async ValueTask<int> ExampleAsync() { ... }
```
@ -64,11 +59,12 @@ This allows the attribute to be applied on methods or local functions or lambdas
Example of usage on a method:
```C#
[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<int>))] // new usage, referring to some custom builder type
[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] // new usage, referring to some custom builder type
static async ValueTask<int> ExampleAsync() { ... }
```
It is an error to apply the attribute multiple times on a given method.
It is an error to apply the attribute multiple times on a given method.
It is an error to apply the attribute to a lambda with an implicit return type.
A developer who wants to use a specific custom builder for all of their methods can do so by putting the relevant attribute on each method.
@ -80,16 +76,20 @@ When compiling an async method, the builder type is determined by:
If an `AsyncMethodBuilder` attribute is present, we take the builder type specified by the attribute and construct it if necessary.
If the override type is an open generic type, take the single type argument of the async method's return type and substitute it into the override type.
If the override type is a bound generic type, then we produce an error.
If the async method's return type does not have a single type argument, then we produce an error.
We verify that the builder type is compatible with the return type of the async method:
1. look for the public `Create` method with no type parameters and no parameters on the constructed override type.
1. look for the public `Create` method with no type parameters and no parameters on the constructed builder type.
It is an error if the method is not found.
2. consider the return type of that `Create` method (a builder type) and look for the public `Task` property.
It is an error if the method returns a type other than the constructed builder type.
2. look for the public `Task` property.
It is an error if the property is not found.
3. consider the type of that `Task` property (a task-like type):
It is an error if the task-like type does not matches the return type of the async method.
Note that it is not necessary for the return type of the method to be a task-like type.
### Execution
The builder type determined above is used as part of the existing async method design.
@ -114,14 +114,14 @@ static ValueTask<int> ExampleAsync()
With this change, if the developer wrote:
```C#
[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<int>))] // new usage, referring to some custom builder type
[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] // new usage, referring to some custom builder type
static async ValueTask<int> ExampleAsync() { ... }
```
it would instead be compiled to:
```C#
[AsyncStateMachine(typeof(<ExampleAsync>d__29))]
[CompilerGenerated]
[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<int>))] // retained but not necessary anymore
[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] // retained but not necessary anymore
static ValueTask<int> ExampleAsync()
{
<ExampleAsync>d__29 stateMachine;

View file

@ -1,10 +1,5 @@
# CallerArgumentExpression
* [x] Proposed
* [ ] Prototype: Not Started
* [ ] Implementation: Not Started
* [ ] Specification: Not Started
## Summary
[summary]: #summary

View file

@ -1,10 +1,5 @@
# Constant Interpolated Strings
* [x] Proposed
* [ ] Prototype: [Not Started](https://github.com/kevinsun-dev/roslyn/BRANCH_NAME)
* [ ] Implementation: [Not Started](https://github.com/dotnet/roslyn/BRANCH_NAME)
* [ ] Specification: [Not Started](pr/1)
## Summary
[summary]: #summary
@ -86,19 +81,19 @@ Whenever an expression fulfills the requirements listed above, the expression is
The compile-time evaluation of constant expressions uses the same rules as run-time evaluation of non-constant expressions, except that where run-time evaluation would have thrown an exception, compile-time evaluation causes a compile-time error to occur.
Unless a constant expression is explicitly placed in an `unchecked` context, overflows that occur in integral-type arithmetic operations and conversions during the compile-time evaluation of the expression always cause compile-time errors ([Constant expressions](../spec/expressions.md#constant-expressions)).
Unless a constant expression is explicitly placed in an `unchecked` context, overflows that occur in integral-type arithmetic operations and conversions during the compile-time evaluation of the expression always cause compile-time errors ([Constant expressions](../../spec/expressions.md#constant-expressions)).
Constant expressions occur in the contexts listed below. In these contexts, a compile-time error occurs if an expression cannot be fully evaluated at compile-time.
* Constant declarations ([Constants](../spec/classes.md#constants)).
* Enumeration member declarations ([Enum members](../spec/enums.md#enum-members)).
* Default arguments of formal parameter lists ([Method parameters](../spec/classes.md#method-parameters))
* `case` labels of a `switch` statement ([The switch statement](../spec/statements.md#the-switch-statement)).
* `goto case` statements ([The goto statement](../spec/statements.md#the-goto-statement)).
* Dimension lengths in an array creation expression ([Array creation expressions](../spec/expressions.md#array-creation-expressions)) that includes an initializer.
* Attributes ([Attributes](../spec/attributes.md)).
* Constant declarations ([Constants](../../spec/classes.md#constants)).
* Enumeration member declarations ([Enum members](../../spec/enums.md#enum-members)).
* Default arguments of formal parameter lists ([Method parameters](../../spec/classes.md#method-parameters))
* `case` labels of a `switch` statement ([The switch statement](../../spec/statements.md#the-switch-statement)).
* `goto case` statements ([The goto statement](../../spec/statements.md#the-goto-statement)).
* Dimension lengths in an array creation expression ([Array creation expressions](../../spec/expressions.md#array-creation-expressions)) that includes an initializer.
* Attributes ([Attributes](../../spec/attributes.md)).
An implicit constant expression conversion ([Implicit constant expression conversions](conversions.md#implicit-constant-expression-conversions)) permits a constant expression of type `int` to be converted to `sbyte`, `byte`, `short`, `ushort`, `uint`, or `ulong`, provided the value of the constant expression is within the range of the destination type.
An implicit constant expression conversion ([Implicit constant expression conversions](../../spec/conversions.md#implicit-constant-expression-conversions)) permits a constant expression of type `int` to be converted to `sbyte`, `byte`, `short`, `ushort`, `uint`, or `ulong`, provided the value of the constant expression is within the range of the destination type.
## Drawbacks
[drawbacks]: #drawbacks

View file

@ -0,0 +1,338 @@
# Enhanced #line directives
## Summary
[summary]: #summary
The compiler applies the mapping defined by `#line` directives to diagnostic locations and sequence points emitted to the PDB.
Currently only the line number and file path can be mapped while the starting character is inferred from the source code. The proposal is to allow specifying full span mapping.
## Motivation
[motivation]: #motivation
DSLs that generate C# source code (such as ASP.NET Razor) can't currently produce precise source mapping using `#line` directives. This results in degraded debugging experience in some cases as the sequence points emitted to the PDB can't map to the precise location in the original source code.
For example, the following Razor code
```
@page "/"
Time: @DateTime.Now
```
generates code like so (simplified):
```C#
#line hidden
void Render()
{
_builder.Add("Time:");
#line 2 "page.razor"
_builder.Add(DateTime.Now);
#line hidden
}
```
The above directive would map the sequence point emitted by the compiler for the `_builder.Add(DateTime.Now);` statement to the line 2, but the column would be off (16 instead of 7).
The Razor source generator actually incorrectly generates the following code:
```C#
#line hidden
void Render()
{
_builder.Add("Time:");
_builder.Add(
#line 2 "page.razor"
DateTime.Now
#line hidden
);
}
```
The intent was to preserve the starting character and it works for diagnostic location mapping. However, this does not work for sequence points since `#line` directive only applies to the sequence points that follow it. There is no sequence point in the middle of the `_builder.Add(DateTime.Now);` statement (sequence points can only be emitted at IL instructions with empty evaluation stack). The `#line 2` directive in above code thus has no effect on the generated PDB and the debugger won't place a breakpoint or stop on the `@DateTime.Now` snippet in the Razor page.
Issues addressed by this proposal:
https://github.com/dotnet/roslyn/issues/43432
https://github.com/dotnet/roslyn/issues/46526
## Detailed design
[design]: #detailed-design
We amend the syntax of `line_indicator` used in `pp_line` directive like so:
Current:
```
line_indicator
: decimal_digit+ whitespace file_name
| decimal_digit+
| 'default'
| 'hidden'
;
```
Proposed:
```
line_indicator
: '(' decimal_digit+ ',' decimal_digit+ ')' '-' '(' decimal_digit+ ',' decimal_digit+ ')' decimal_digit+ whitespace file_name
| '(' decimal_digit+ ',' decimal_digit+ ')' '-' '(' decimal_digit+ ',' decimal_digit+ ')' file_name
| decimal_digit+ whitespace file_name
| decimal_digit+
| 'default'
| 'hidden'
;
```
That is, the `#line` directive would accept either 5 decimal numbers (_start line_, _start character_, _end line_, _end character_, _character offset_),
4 decimal numbers (_start line_, _start character_, _end line_, _end character_), or a single one (_line_).
If _character offset_ is not specified its default value is 0, otherwise it specifies the number of UTF-16 characters. The number must be non-negative and less then length of the line following the #line directive in the unmapped file.
(_start line_, _start character_)-(_end line_, _end character_) specifies a span in the mapped file. _start line_ and _end line_ are positive integers that specify line numbers. _start character_, _end character_ are positive integers that specify UTF-16 character numbers. _start line_, _start character_, _end line_, _end character_ are 1-based, meaning that the first line of the file and the first UTF-16 character on each line is assigned number 1.
> The implementation would constraint these numbers so that they specify a valid [sequence point source span](https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md#sequence-points-blob):
> - _start line_ - 1 is within range [0, 0x20000000) and not equal to 0xfeefee.
> - _end line_ - 1 is within range [0, 0x20000000) and not equal to 0xfeefee.
> - _start character_ - 1 is within range [0, 0x10000)
> - _end character_ - 1 is within range [0, 0x10000)
> - _end line_ is greater or equal to _start line_.
> - _start line_ is equal to _end line_ then _end character_ is greater than _start character_.
> Note that the numbers specified in the directive syntax are 1-based numbers but the actual spans in the PDB are zero-based. Hence the -1 adjustments above.
The mapped spans of sequence points and the locations of diagnostics that `#line` directive applies to are calculated as follows.
Let _d_ be the zero-based number of the unmapped line containing the `#line` directive.
Let span L = (start: (_start line_ - 1, _start character_ - 1), end: (_end line_ - 1, _end character_ - 1)) be zero-based span specified by the directive.
Function M that maps a position (line, character) within the scope of the `#line` directive in the source file containing the #line directive to a mapped position (mapped line, mapped character) is defined as follows:
_M_(_l_, _c_) =
_l_ = _d_ + 1 => (_L.start.line_ + _l_ _d_ 1, _L.start.character_ + max(_c_ _character offset_, 0))
_l_ > _d_ + 1 => (_L.start.line_ + _l_ _d_ 1, _c_)
The syntax constructs that sequence points are associated with are determined by the compiler implementation and not covered by this specification.
The compiler also decides for each sequence point its unmapped span. This span may partially or fully cover the associated syntax construct.
Once the unmapped spans are determined by the compiler the function _M_ defined above is applied to their starting and ending positions, with the exception of the ending position of all sequence points within the scope of the #line directive whose unmapped location is at line _d_ + 1 and character less than character offset. The end position of all these sequence points is _L.end_.
> Example [5.i] demonstrates why it is necessary to provide the ability to specify the end position of the first sequence point span.
> The above definition allows the generator of the unmapped source code to avoid intimate knowledge of which exact source constructs of the C# language produce sequence points. The mapped spans of the sequence points in the scope of the `#line` directive are derived from the relative position of the corresponding unmapped spans to the first unmapped span.
> Specifying the _character offset_ allows the generator to insert any single-line prefix on the first line. This prefix is generated code that is not present in the mapped file. Such inserted prefix affects the value of the first unmapped sequence point span. Therefore the starting character of subsequent sequence point spans need to be offset by the length of the prefix (_character offset_). See example [2].
![image](https://user-images.githubusercontent.com/41759/120511514-563c7e00-c37f-11eb-9436-20c2def68932.png)
### Examples
For clarity the examples use `spanof('...')` and `lineof('...')` pseudo-syntax to express the mapped span start position and line number, respectively, of the specified code snippet.
#### 1. First and subsequent spans
Consider the following code with unmapped zero-based line numbers listed on the right:
```
#line 1 10 1 15 "a" // 3
A();B( // 4
);C(); // 5
D(); // 6
```
d = 3
L = (0, 9)..(0, 14)
There are 4 sequence point spans the directive applies to with following unmapped and mapped spans:
(4, 2)..(4, 5) => (0, 9)..(0, 14)
(4, 6)..(5, 1) => (0, 15)..(1, 1)
(5, 2)..(5, 5) => (1, 2)..(1, 5)
(6, 4)..(6, 7) => (2, 4)..(2, 7)
#### 2. Character offset
Razor generates ` _builder.Add(` prefix of length 15 (including two leading spaces).
Razor:
```
@page "/"
@F(() => 1+1,
() => 2+2
)
```
Generated C#:
```C#
#line hidden
void Render()
{
#line spanof('F(...)') 15 "page.razor" // 4
_builder.Add(F(() => 1+1, // 5
() => 2+2 // 6
)); // 7
#line hidden
}
);
}
```
d = 4
L = (1, 1)..(3,0)
_character offset_ = 15
Spans:
- `_builder.Add(F(…));` => `F(…)`: (5, 2)..(7, 2) => (1, 1)..(3, 0)
- `1+1` => `1+1`: (5, 23)..(5, 25) => (1, 9)..(1, 11)
- `2+2` => `2+2`: (6, 7)..(6, 9) => (2, 7)..(2, 9)
#### 3. Razor: Single-line span
Razor:
```
@page "/"
Time: @DateTime.Now
```
Generated C#:
```C#
#line hidden
void Render()
{
_builder.Add("Time:");
#line spanof('DateTime.Now') 15 "page.razor"
_builder.Add(DateTime.Now);
#line hidden
);
}
```
#### 4. Razor: Multi-line span
Razor:
```
@page "/"
@JsonToHtml(@"
{
""key1"": "value1",
""key2"": "value2"
}")
```
Generated C#:
```C#
#line hidden
void Render()
{
_builder.Add("Time:");
#line spanof('JsonToHtml(@"...")') 15 "page.razor"
_builder.Add(JsonToHtml(@"
{
""key1"": "value1",
""key2"": "value2"
}"));
#line hidden
}
);
}
```
#### 5. Razor: block constructs
##### i. block containing expressions
In this example, the mapped span of the first sequence point that is associated with the IL instruction that is emitted for the `_builder.Add(Html.Helper(() => ` statement
needs to cover the whole expression of `Html.Helper(...)` in the generated file `a.razor`. This is achieved by application of rule [1] to the end position of the sequence point.
```
@Html.Helper(() =>
{
<p>Hello World</p>
@DateTime.Now
})
```
```C#
#line spanof('Html.Helper(() => { ... })') 13 "a.razor"
_builder.Add(Html.Helper(() =>
#line lineof('{') "a.razor"
{
#line spanof('DateTime.Now') 13 "a.razor"
_builder.Add(DateTime.Now);
#line lineof('}') "a.razor"
}
#line hidden
)
```
##### ii. block containing statements
Uses existing `#line line file` form since
a) Razor does not add any prefix,
b) `{` is not present in the generated file and there can't be a sequence point placed on it, therefore the span of the first unmapped sequence point is unknown to Razor.
The starting character of `Console` in the generated file must be aligned with the Razor file.
```
@{Console.WriteLine(1);Console.WriteLine(2);}
```
```C#
#line lineof('@{') "a.razor"
Console.WriteLine(1);Console.WriteLine(2);
#line hidden
```
##### iii. block containing top-level code (@code, @functions)
Uses existing `#line line file` form since
a) Razor does not add any prefix,
b) `{` is not present in the generated file and there can't be a sequence point placed on it, therefore the span of the first unmapped sequence point is unknown to Razor.
The starting character of `[Parameter]` in the generated file must be aligned with the Razor file.
```
@code {
[Parameter]
public int IncrementAmount { get; set; }
}
```
```C#
#line lineof('[') "a.razor"
[Parameter]
public int IncrementAmount { get; set; }
#line hidden
```
#### 6. Razor: `@for`, `@foreach`, `@while`, `@do`, `@if`, `@switch`, `@using`, `@try`, `@lock`
Uses existing `#line line file` form since
a) Razor does not add any prefix.
b) the span of the first unmapped sequence point may not be known to Razor (or shouldn't need to know).
The starting character of the keyword in the generated file must be aligned with the Razor file.
```
@for (var i = 0; i < 10; i++)
{
}
@if (condition)
{
}
else
{
}
```
```C#
#line lineof('for') "a.razor"
for (var i = 0; i < 10; i++)
{
}
#line lineof('if') "a.razor"
if (condition)
{
}
else
{
}
#line hidden
```

View file

@ -48,4 +48,4 @@ For example, a pattern of the form `{ Prop1.Prop2: pattern }` is exactly equival
Note that this will include the null check when *T* is a nullable value type or a reference type. This null check means that the nested properties available will be the properties of *T0*, not of *T*.
Repeated member paths are allowed. Under the hood, such member accesses are simplified to be evaluated once.
Repeated member paths are allowed. The compilation of pattern matching can take advantage of common parts of patterns.

View file

@ -1,11 +1,6 @@
## File Scoped Namespaces
# File Scoped Namespaces
- [ ] Proposed
- [ ] Prototype: Started
- [ ] Implementation: Started
- [ ] Specification: Started
### Summary
## Summary
Allow a simpler format for the common case of file containing only one namespace in it. This format is `namespace X.Y.Z;` (note the semicolon and lack of braces). This allows for files like so:
@ -21,7 +16,7 @@ class X
The semantics are that using the `namespace X.Y.Z;` form is equivalent to writing `namespace X.Y.Z { ... }` where the remainder of the file following the file-scoped namespace is in the `...` section of a standard namespace declaration.
### Motivation
## Motivation
Analysis of the C# ecosystem shows that around 99.7% (or more) files are all of either one of these forms:
@ -49,11 +44,11 @@ However, both these forms force the user to indent the majority of their code an
The primary goal of the feature therefore is to meet the needs of the majority of the ecosystem with less unnecessary boilerplate.
### Detailed design
## Detailed design
This proposal takes the form of a diff to the existing https://github.com/dotnet/csharplang/blob/main/spec/namespaces.md#compilation-units section of the specification.
#### Diff
### Diff
~~A *compilation_unit* defines the overall structure of a source file. A compilation unit consists of zero or more *using_directive*s followed by zero or more *global_attributes* followed by zero or more *namespace_member_declaration*s.~~

View file

@ -0,0 +1,61 @@
# Generic Attributes
## Summary
[summary]: #summary
When generics were introduced in C# 2.0, attribute classes were not allowed to participate. We can make the language more composable by removing (rather, loosening) this restriction. The .NET Core runtime has added support for generic attributes. Now, all that's missing is support for generic attributes in the compiler.
## Motivation
[motivation]: #motivation
Currently attribute authors can take a `System.Type` as a parameter and have users pass a `typeof` expression to provide the attribute with types that it needs. However, outside of analyzers, there's no way for an attribute author to constrain what types are allowed to be passed to an attribute via `typeof`. If attributes could be generic, then attribute authors could use the existing system of type parameter constraints to express the requirements for the types they take as input.
## Detailed design
[design]: #detailed-design
The following section is amended: https://github.com/dotnet/csharplang/blob/main/spec/classes.md#base-classes
> The direct base class of a class type must not be any of the following types: System.Array, System.Delegate, System.MulticastDelegate, System.Enum, or System.ValueType. ~~Furthermore, a generic class declaration cannot use System.Attribute as a direct or indirect base class.~~
One important note is that the following section of the spec is *unaffected* when referencing the point of usage of an attribute, i.e. within an attribute list: https://github.com/dotnet/csharplang/blob/main/spec/types.md#type-parameters
> A type parameter cannot be used anywhere within an attribute.
This means that when a generic attribute is used, its construction needs to be fully "closed", i.e. not containing any type parameters, which means the following is still disallowed:
```cs
using System;
using System.Collections.Generic;
public class Attr<T1> : Attribute { }
public class Program<T2>
{
[Attr<T2>] // error
[Attr<List<T2>>] // error
void M() { }
}
```
When a generic attribute is used in an attribute list, its type arguments have the same restrictions that `typeof` has on its argument. For example, `[Attr<dynamic>]` is an error. This is because "attribute-dependent" types like `dynamic`, `List<string?>`, `nint`, and so on can't be fully represented in the final IL for an attribute type argument, because there isn't a symbol to "attach" the `DynamicAttribute` or other well-known attribute to.
## Drawbacks
[drawbacks]: #drawbacks
Removing the restriction, reasoning out the implications, and adding the appropriate tests is work.
## Alternatives
[alternatives]: #alternatives
Attribute authors who want users to be able to discover the requirements for the types they provide to attributes need to write analyzers and guide their users to use those analyzers in their builds.
## Unresolved questions
[unresolved]: #unresolved-questions
- [x] What does `AllowMultiple = false` mean on a generic attribute? If we have `[Attr<string>]` and `[Attr<object>]` both used on a symbol, does that mean "multiple" of the attribute are in use?
- For now we are inclined to take the more restrictive route here and consider the attribute class's original definition when deciding whether multiple of it have been applied. In other words, `[Attr<string>]` and `[Attr<object>]` applied together is incompatible with `AllowMultiple = false`.
## Design meetings
- https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-02-21.md#generic-attributes
- At the time there was a concern that we would have to gate the feature on whether the target runtime supports it. (However, we now only support C# 10 on .NET 6. It would be nice to for the implementation to be aware of what minimum target framework supports the feature, but seems less essential today.)

View file

@ -1,7 +1,7 @@
# Improved Definite Assignment Analysis
## Summary
[Definite assignment analysis](../spec/variables.md#definite-assignment) as specified has a few gaps which have caused users inconvenience. In particular, scenarios involving comparison to boolean constants, conditional-access, and null coalescing.
[Definite assignment analysis](../../spec/variables.md#definite-assignment) as specified has a few gaps which have caused users inconvenience. In particular, scenarios involving comparison to boolean constants, conditional-access, and null coalescing.
## Related discussions and issues
csharplang discussion of this proposal: https://github.com/dotnet/csharplang/discussions/4240
@ -94,14 +94,14 @@ if (c != null ? c.M(out object obj4) : false)
}
```
# Specification
## Specification
## ?. (null-conditional operator) expressions
We introduce a new section **?. (null-conditional operator) expressions**. See the [null-conditional operator](../spec/expressions.md#null-conditional-operator) specification and [definite assignment rules](../spec/variables.md#precise-rules-for-determining-definite-assignment) for context.
### ?. (null-conditional operator) expressions
We introduce a new section **?. (null-conditional operator) expressions**. See the [null-conditional operator](../../spec/expressions.md#null-conditional-operator) specification and [definite assignment rules](../../spec/variables.md#precise-rules-for-determining-definite-assignment) for context.
As in the definite assignment rules linked above, we refer to a given initially unassigned variable as *v*.
We introduce the concept of "directly contains". An expression *E* is said to "directly contain" a subexpression *E<sub>1</sub>* if it is not subject to a [user-defined conversion](../spec/conversions.md#user-defined-conversions) whose parameter is not of a non-nullable value type, and one of the following conditions holds:
We introduce the concept of "directly contains". An expression *E* is said to "directly contain" a subexpression *E<sub>1</sub>* if it is not subject to a [user-defined conversion](../../spec/conversions.md#user-defined-conversions) whose parameter is not of a non-nullable value type, and one of the following conditions holds:
- *E* is *E<sub>1</sub>*. For example, `a?.b()` directly contains the expression `a?.b()`.
- If *E* is a parenthesized expression `(E2)`, and *E<sub>2</sub>* directly contains *E<sub>1</sub>*.
- If *E* is a null-forgiving operator expression `E2!`, and *E<sub>2</sub>* directly contains *E<sub>1</sub>*.
@ -114,7 +114,7 @@ In subsequent sections we will refer to *E<sub>0</sub>* as the *non-conditional
- The definite assignment state of *v* at any point within *E* is the same as the definite assignment state at the corresponding point within *E0*.
- The definite assignment state of *v* after *E* is the same as the definite assignment state of *v* after *primary_expression*.
### Remarks
#### Remarks
We use the concept of "directly contains" to allow us to skip over relatively simple "wrapper" expressions when analyzing conditional accesses that are compared to other values. For example, `((a?.b(out x))!) == true` is expected to result in the same flow state as `a?.b == true` in general.
We also want to allow analysis to function in the presence of a number of possible conversions on a conditional access. Propagating out "state when not null" is not possible when the conversion is user-defined, though, since we can't count on user-defined conversions to honor the constraint that the output is non-null only if the input is non-null. The only exception to this is when the user-defined conversion's input is a non-nullable value type. For example:
@ -147,7 +147,7 @@ When we consider whether a variable is assigned at a given point within a null-c
For example, given a conditional expression `a?.b(out x)?.c(x)`, the non-conditional counterpart is `a.b(out x).c(x)`. If we want to know the definite assignment state of `x` before `?.c(x)`, for example, then we perform a "hypothetical" analysis of `a.b(out x)` and use the resulting state as an input to `?.c(x)`.
## Boolean constant expressions
### Boolean constant expressions
We introduce a new section "Boolean constant expressions":
For an expression *expr* where *expr* is a constant expression with a bool value:
@ -155,14 +155,14 @@ For an expression *expr* where *expr* is a constant expression with a bool value
- If *expr* is a constant expression with value *true*, and the state of *v* before *expr* is "not definitely assigned", then the state of *v* after *expr* is "definitely assigned when false".
- If *expr* is a constant expression with value *false*, and the state of *v* before *expr* is "not definitely assigned", then the state of *v* after *expr* is "definitely assigned when true".
### Remarks
#### Remarks
We assume that if an expression has a constant value bool `false`, for example, it's impossible to reach any branch that requires the expression to return `true`. Therefore variables are assumed to be definitely assigned in such branches. This ends up combining nicely with the spec changes for expressions like `??` and `?:` and enabling a lot of useful scenarios.
It's also worth noting that we never expect to be in a conditional state *before* visiting a constant expression. That's why we do not account for scenarios such as "*expr* is a constant expression with value *true*, and the state of *v* before *expr* is "definitely assigned when true".
## ?? (null-coalescing expressions) augment
We augment the section [?? (null coalescing) expressions](../spec/variables.md#-null-coalescing-expressions) as follows:
### ?? (null-coalescing expressions) augment
We augment the section [?? (null coalescing) expressions](../../spec/variables.md#-null-coalescing-expressions) as follows:
For an expression *expr* of the form `expr_first ?? expr_second`:
- ...
@ -170,7 +170,7 @@ For an expression *expr* of the form `expr_first ?? expr_second`:
- ...
- If *expr_first* directly contains a null-conditional expression *E*, and *v* is definitely assigned after the non-conditional counterpart *E<sub>0</sub>*, then the definite assignment state of *v* after *expr* is the same as the definite assignment state of *v* after *expr_second*.
### Remarks
#### Remarks
The above rule formalizes that for an expression like `a?.M(out x) ?? (x = false)`, either the `a?.M(out x)` was fully evaluated and produced a non-null value, in which case `x` was assigned, or the `x = false` was evaluated, in which case `x` was also assigned. Therefore `x` is always assigned after this expression.
This also handles the `dict?.TryGetValue(key, out var value) ?? false` scenario, by observing that *v* is definitely assigned after `dict.TryGetValue(key, out var value)`, and *v* is "definitely assigned when true" after `false`, and concluding that *v* must be "definitely assigned when true".
@ -179,8 +179,8 @@ The more general formulation also allows us to handle some more unusual scenario
- `if (x?.M(out y) ?? (b && z.M(out y))) y.ToString();`
- `if (x?.M(out y) ?? z?.M(out y) ?? false) y.ToString();`
## ?: (conditional) expressions
We augment the section [**?: (conditional) expressions**](../spec/variables.md#-conditional-expressions) as follows:
### ?: (conditional) expressions
We augment the section [**?: (conditional) expressions**](../../spec/variables.md#-conditional-expressions) as follows:
For an expression *expr* of the form `expr_cond ? expr_true : expr_false`:
- ...
@ -189,7 +189,7 @@ For an expression *expr* of the form `expr_cond ? expr_true : expr_false`:
- If the state of *v* after *expr_true* is "definitely assigned when true", and the state of *v* after *expr_false* is "definitely assigned when true", then the state of *v* after *expr* is "definitely assigned when true".
- If the state of *v* after *expr_true* is "definitely assigned when false", and the state of *v* after *expr_false* is "definitely assigned when false", then the state of *v* after *expr* is "definitely assigned when false".
### Remarks
#### Remarks
This makes it so when both arms of a conditional expression result in a conditional state, we join the corresponding conditional states and propagate it out instead of unsplitting the state and allowing the final state to be non-conditional. This enables scenarios like the following:
@ -207,18 +207,18 @@ bool Set(out int x) { x = 0; return true; }
This is an admittedly niche scenario, that compiles without error in the native compiler, but was broken in Roslyn in order to match the specification at the time. See [internal issue](http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/529603).
## ==/!= (relational equality operator) expressions
### ==/!= (relational equality operator) expressions
We introduce a new section **==/!= (relational equality operator) expressions**.
The [general rules for expressions with embedded expressions](../spec/variables.md#general-rules-for-expressions-with-embedded-expressions) apply, except for the scenarios described below.
The [general rules for expressions with embedded expressions](../../spec/variables.md#general-rules-for-expressions-with-embedded-expressions) apply, except for the scenarios described below.
For an expression *expr* of the form `expr_first == expr_second`, where `==` is a [predefined comparison operator](../spec/expressions.md#relational-and-type-testing-operators) or a [lifted operator](../spec/expressions.md#lifted-operators), the definite assignment state of *v* after *expr* is determined by:
For an expression *expr* of the form `expr_first == expr_second`, where `==` is a [predefined comparison operator](../../spec/expressions.md#relational-and-type-testing-operators) or a [lifted operator](../../spec/expressions.md#lifted-operators), the definite assignment state of *v* after *expr* is determined by:
- If *expr_first* directly contains a null-conditional expression *E* and *expr_second* is a constant expression with value *null*, and the state of *v* after the non-conditional counterpart *E<sub>0</sub>* is "definitely assigned", then the state of *v* after *expr* is "definitely assigned when false".
- If *expr_first* directly contains a null-conditional expression *E* and *expr_second* is an expression of a non-nullable value type, or a constant expression with a non-null value, and the state of *v* after the non-conditional counterpart *E<sub>0</sub>* is "definitely assigned", then the state of *v* after *expr* is "definitely assigned when true".
- If *expr_first* is of type *boolean*, and *expr_second* is a constant expression with value *true*, then the definite assignment state after *expr* is the same as the definite assignment state after *expr_first*.
- If *expr_first* is of type *boolean*, and *expr_second* is a constant expression with value *false*, then the definite assignment state after *expr* is the same as the definite assignment state of *v* after the logical negation expression `!expr_first`.
For an expression *expr* of the form `expr_first != expr_second`, where `!=` is a [predefined comparison operator](../spec/expressions.md#relational-and-type-testing-operators) or a [lifted operator](../spec/expressions.md#lifted-operators), the definite assignment state of *v* after *expr* is determined by:
For an expression *expr* of the form `expr_first != expr_second`, where `!=` is a [predefined comparison operator](../../spec/expressions.md#relational-and-type-testing-operators) or a [lifted operator](../../spec/expressions.md#lifted-operators), the definite assignment state of *v* after *expr* is determined by:
- If *expr_first* directly contains a null-conditional expression *E* and *expr_second* is a constant expression with value *null*, and the state of *v* after the non-conditional counterpart *E<sub>0</sub>* is "definitely assigned", then the state of *v* after *expr* is "definitely assigned when true".
- If *expr_first* directly contains a null-conditional expression *E* and *expr_second* is an expression of a non-nullable value type, or a constant expression with a non-null value, and the state of *v* after the non-conditional counterpart *E<sub>0</sub>* is "definitely assigned", then the state of *v* after *expr* is "definitely assigned when false".
- If *expr_first* is of type *boolean*, and *expr_second* is a constant expression with value *true*, then the definite assignment state after *expr* is the same as the definite assignment state of *v* after the logical negation expression `!expr_first`.
@ -226,7 +226,7 @@ For an expression *expr* of the form `expr_first != expr_second`, where `!=` is
All of the above rules in this section are commutative, meaning that if a rule applies when evaluated in the form `expr_second op expr_first`, it also applies in the form `expr_first op expr_second`.
### Remarks
#### Remarks
The general idea expressed by these rules is:
- if a conditional access is compared to `null`, then we know the operations definitely occurred if the result of the comparison is `false`
- if a conditional access is compared to a non-nullable value type or a non-null constant, then we know the operations definitely occurred if the result of the comparison is `true`.
@ -244,24 +244,24 @@ Some consequences of these rules:
- `if (a?.b(out var x) != false)) x() else x();` will error in the 'then' branch
- `if (a?.b(out var x) != null)) x() else x();` will error in the 'else' branch
## `is` operator and `is` pattern expressions
### `is` operator and `is` pattern expressions
We introduce a new section **`is` operator and `is` pattern expressions**.
For an expression *expr* of the form `E is T`, where *T* is any type or pattern
- The definite assignment state of *v* before *E* is the same as the definite assignment state of *v* before *expr*.
- The definite assignment state of *v* after *expr* is determined by:
- If *E* directly contains a null-conditional expression, and the state of *v* after the non-conditional counterpart *E<sub>0</sub>* is "definitely assigned", and `T` is any type or a pattern that only matches a non-null input, then the state of *v* after *expr* is "definitely assigned when true".
- If *E* directly contains a null-conditional expression, and the state of *v* after the non-conditional counterpart *E<sub>0</sub>* is "definitely assigned", and `T` is a pattern which only matches a null input, then the state of *v* after *expr* is "definitely assigned when false".
- If *E* directly contains a null-conditional expression, and the state of *v* after the non-conditional counterpart *E<sub>0</sub>* is "definitely assigned", and `T` is any type or a pattern that does not match a `null` input, then the state of *v* after *expr* is "definitely assigned when true".
- If *E* directly contains a null-conditional expression, and the state of *v* after the non-conditional counterpart *E<sub>0</sub>* is "definitely assigned", and `T` is a pattern that matches a `null` input, then the state of *v* after *expr* is "definitely assigned when false".
- If *E* is of type boolean and `T` is a pattern which only matches a `true` input, then the definite assignment state of *v* after *expr* is the same as the definite assignment state of *v* after E.
- If *E* is of type boolean and `T` is a pattern which only matches a `false` input, then the definite assignment state of *v* after *expr* is the same as the definite assignment state of *v* after the logical negation expression `!expr`.
- Otherwise, if the definite assignment state of *v* after E is "definitely assigned", then the definite assignment state of *v* after *expr* is "definitely assigned".
### Remarks
#### Remarks
This section is meant to address similar scenarios as in the `==`/`!=` section above.
This specification does not address recursive patterns, e.g. `(a?.b(out x), c?.d(out y)) is (object, object)`. Such support may come later if time permits.
# Additional scenarios
## Additional scenarios
This specification doesn't currently address scenarios involving pattern switch expressions and switch statements. For example:

View file

@ -0,0 +1,645 @@
# Improved Interpolated Strings
## Summary
We introduce a new pattern for creating and using interpolated string expressions to allow for efficient formatting and use in both general `string` scenarios
and more specialized scenarios such as logging frameworks, without incurring unnecessary allocations from formatting the string in the framework.
## Motivation
Today, string interpolation mainly lowers down to a call to `string.Format`. This, while general purpose, can be inefficient for a number of reasons:
1. It boxes any struct arguments, unless the runtime has happened to introduce an overload of `string.Format` that takes exactly the correct types of arguments
in exactly the correct order.
* This ordering is why the runtime is hesitant to introduce generic versions of the method, as it would lead to combinatoric explosion of generic instantiations
of a very common method.
2. It has to allocate an array for the arguments in most cases.
3. There is no opportunity to avoid instanciating the instance if it's not needed. Logging frameworks, for example, will recommend avoiding string interpolation
because it will cause a string to be realized that may not be needed, depending on the current log-level of the application.
4. It can never use `Span` or other ref struct types today, because ref structs are not allowed as generic type parameters, meaning that if a user wants to avoid
copying to intermediate locations they have to manually format strings.
Internally, the runtime has a type called `ValueStringBuilder` to help deal with the first 2 of these scenarios. They pass a stackalloc'd buffer to the builder,
repeatedly call `AppendFormat` with every part, and then get a final string out. If the resulting string goes past the bounds of the stack buffer, they can then
move to an array on the heap. However, this type is dangerous to expose directly, as incorrect usage could lead to a rented array to be double-disposed, which
then will cause all sorts of undefined behavior in the program as two locations think they have sole access to the rented array. This proposal creates a way to
use this type safely from native C# code by just writing an interpolated string literal, leaving written code unchanged while improving every interpolated string
that a user writes. It also extends this pattern to allow for interpolated strings passed as arguments to other methods to use a handler pattern, defined by
receiver of the method, that will allow things like logging frameworks to avoid allocating strings that will never be needed, and giving C# users familiar,
convenient interpolation syntax.
## Detailed Design
### The handler pattern
We introduce a new handler pattern that can represent an interpolated string passed as an argument to a method. The simple English of the pattern is as follows:
When an _interpolated\_string\_expression_ is passed as an argument to a method, we look at the type of the parameter. If the parameter type has a constructor
that can be invoked with 2 int parameters, `literalLength` and `formattedCount`, optionally takes additional parameters specified by an attribute on the original
parameter, optionally has an out boolean trailing parameter, and the type of the original parameter has instance `AppendLiteral` and `AppendFormatted` methods that
can be invoked for every part of the interpolated string, then we lower the interpolation using that, instead of into a traditional call to
`string.Format(formatStr, args)`. A more concrete example is helpful for picturing this:
```cs
// The handler that will actually "build" the interpolated string"
[InterpolatedStringHandler]
public ref struct TraceLoggerParamsInterpolatedStringHandler
{
// Storage for the built-up string
private bool _logLevelEnabled;
public TraceLoggerParamsInterpolatedStringHandler(int literalLength, int formattedCount, Logger logger, out bool handlerIsValid)
{
if (!logger._logLevelEnabled)
{
handlerIsValid = false;
return;
}
handlerIsValid = true;
_logLevelEnabled = logger.EnabledLevel;
}
public void AppendLiteral(string s)
{
// Store and format part as required
}
public void AppendFormatted<T>(T t)
{
// Store and format part as required
}
}
// The logger class. The user has an instance of this, accesses it via static state, or some other access
// mechanism
public class Logger
{
// Initialization code omitted
public LogLevel EnabledLevel;
public void LogTrace([InterpolatedStringHandlerArguments("")]TraceLoggerParamsInterpolatedStringHandler handler)
{
// Impl of logging
}
}
Logger logger = GetLogger(LogLevel.Info);
// Given the above definitions, usage looks like this:
var name = "Fred Silberberg";
logger.LogTrace($"{name} will never be printed because info is < trace!");
// This is converted to:
var name = "Fred Silberberg";
var receiverTemp = logger;
var handler = new TraceLoggerParamsInterpolatedStringHandler(literalLength: 47, formattedCount: 1, receiverTemp, out var handlerIsValid);
if (handlerIsValid)
{
handler.AppendFormatted(name);
handler.AppendLiteral(" will never be printed because info is < trace!");
}
receiverTemp.LogTrace(handler);
```
Here, because `TraceLoggerParamsInterpolatedStringHandler` has a constructor with the correct parameters, we say that the interpolated string
has an implicit handler conversion to that parameter, and it lowers to the pattern shown above. The specese needed for this is a bit complicated,
and is expanded below.
The rest of this proposal will use `Append...` to refer to either of `AppendLiteral` or `AppendFormatted` in cases when both are applicable.
#### New attributes
The compiler recognizes the `System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute`:
```cs
using System;
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
public sealed class InterpolatedStringHandlerAttribute : Attribute
{
public InterpolatedStringHandlerAttribute()
{
}
}
}
```
This attribute is used by the compiler to determine if a type is a valid interpolated string handler type.
The compiler also recognizes the `System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute`:
```cs
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class InterpolatedStringHandlerArgumentAttribute : Attribute
{
public InterpolatedHandlerArgumentAttribute(string argument);
public InterpolatedHandlerArgumentAttribute(params string[] arguments);
public string[] Arguments { get; }
}
}
```
This attribute is used on parameters, to inform the compiler how to lower an interpolated string handler pattern used in a parameter position.
#### Interpolated string handler conversion
Type `T` is said to be an _applicable\_interpolated\_string\_handler\_type_ if it is attributed with `System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute`.
There exists an implicit _interpolated\_string\_handler\_conversion_ to `T` from an _interpolated\_string\_expression_, or an _additive\_expression_ composed entirely of
_interpolated\_string\_expression_s and using only `+` operators.
For simplicity in the rest of this speclet, _interpolated\_string\_expression_ refers to both a simple _interpolated\_string\_expression_, and to an _additive\_expression_ composed
entirely of _interpolated\_string\_expression_s and using only `+` operators.
Note that this conversion always exists, regardless of whether there will be later errors when actually attempting to lower the interpolation using the handler pattern. This is
done to help ensure that there are predictable and useful errors and that runtime behavior doesn't change based on the content of an interpolated string.
#### Applicable function member adjustments
We adjust the wording of the [applicable function member algorithm](https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#applicable-function-member)
as follows (a new sub-bullet is added to each section, in bold):
A function member is said to be an ***applicable function member*** with respect to an argument list `A` when all of the following are true:
* Each argument in `A` corresponds to a parameter in the function member declaration as described in [Corresponding parameters](../../spec/expressions.md#corresponding-parameters), and any parameter to which no argument corresponds is an optional parameter.
* For each argument in `A`, the parameter passing mode of the argument (i.e., value, `ref`, or `out`) is identical to the parameter passing mode of the corresponding parameter, and
* for a value parameter or a parameter array, an implicit conversion ([Implicit conversions](../../spec/conversions.md#implicit-conversions)) exists from the argument to the type of the corresponding parameter, or
* **for a `ref` parameter whose type is a struct type, an implicit _interpolated\_string\_handler\_conversion_ exists from the argument to the type of the corresponding parameter, or**
* for a `ref` or `out` parameter, the type of the argument is identical to the type of the corresponding parameter. After all, a `ref` or `out` parameter is an alias for the argument passed.
For a function member that includes a parameter array, if the function member is applicable by the above rules, it is said to be applicable in its ***normal form***. If a function member that includes a parameter array is not applicable in its normal form, the function member may instead be applicable in its ***expanded form***:
* The expanded form is constructed by replacing the parameter array in the function member declaration with zero or more value parameters of the element type of the parameter array such that the number of arguments in the argument list `A` matches the total number of parameters. If `A` has fewer arguments than the number of fixed parameters in the function member declaration, the expanded form of the function member cannot be constructed and is thus not applicable.
* Otherwise, the expanded form is applicable if for each argument in `A` the parameter passing mode of the argument is identical to the parameter passing mode of the corresponding parameter, and
* for a fixed value parameter or a value parameter created by the expansion, an implicit conversion ([Implicit conversions](../../spec/conversions.md#implicit-conversions)) exists from the type of the argument to the type of the corresponding parameter, or
* **for a `ref` parameter whose type is a struct type, an implicit _interpolated\_string\_handler\_conversion_ exists from the argument to the type of the corresponding parameter, or**
* for a `ref` or `out` parameter, the type of the argument is identical to the type of the corresponding parameter.
Important note: this means that if there are 2 otherwise equivalent overloads, that only differ by the type of the _applicable\_interpolated\_string\_handler\_type_, these overloads will
be considered ambiguous. Further, because we do not see through explicit casts, it is possible that there could arise an unresolvable scenario where both applicable overloads use
`InterpolatedStringHandlerArguments` and are totally uncallable without manually performing the handler lowering pattern. We could potentially make changes to the better function member
algorithm to resolve this if we so choose, but this scenario unlikely to occur and isn't a priority to address.
#### Better conversion from expression adjustments
We change the [better conversion from expression](https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#better-conversion-from-expression) section to the
following:
Given an implicit conversion `C1` that converts from an expression `E` to a type `T1`, and an implicit conversion `C2` that converts from an expression `E` to a type `T2`, `C1` is a ***better conversion*** than `C2` if:
1. `E` is a non-constant _interpolated\_string\_expression_, `C1` is an _implicit\_string\_handler\_conversion_, `T1` is an _applicable\_interpolated\_string\_handler\_type_, and `C2` is not an _implicit\_string\_handler\_conversion_, or
2. `E` does not exactly match `T2` and at least one of the following holds:
* `E` exactly matches `T1` ([Exactly matching Expression](../../spec/expressions.md#exactly-matching-expression))
* `T1` is a better conversion target than `T2` ([Better conversion target](../../spec/expressions.md#better-conversion-target))
This does mean that there are some potentially non-obvious overload resolution rules, depending on whether the interpolated string in question is a constant-expression or not. For example:
```cs
void Log(string s) { ... }
void Log(TraceLoggerParamsInterpolatedStringHandler p) { ... }
Log($""); // Calls Log(string s), because $"" is a constant expression
Log($"{"test"}"); // Calls Log(string s), because $"{"test"}" is a constant expression
Log($"{1}"); // Calls Log(TraceLoggerParamsInterpolatedStringHandler p), because $"{1}" is not a constant expression
```
This is introduced so that things that can simply be emitted as constants do so, and don't incur any overhead, while things that cannot be constant use the handler pattern.
### InterpolatedStringHandler and Usage
We introduce a new type in `System.Runtime.CompilerServices`: `DefaultInterpolatedStringHandler`. This is a ref struct with many of the same semantics as `ValueStringBuilder`,
intended for direct use by the C# compiler. This struct would look approximately like this:
```cs
// API Proposal issue: https://github.com/dotnet/runtime/issues/50601
namespace System.Runtime.CompilerServices
{
[InterpolatedStringHandler]
public ref struct DefaultInterpolatedStringHandler
{
public DefaultInterpolatedStringHandler(int literalLength, int formattedCount);
public string ToStringAndClear();
public void AppendLiteral(string value);
public void AppendFormatted<T>(T value);
public void AppendFormatted<T>(T value, string? format);
public void AppendFormatted<T>(T value, int alignment);
public void AppendFormatted<T>(T value, int alignment, string? format);
public void AppendFormatted(ReadOnlySpan<char> value);
public void AppendFormatted(ReadOnlySpan<char> value, int alignment = 0, string? format = null);
public void AppendFormatted(string? value);
public void AppendFormatted(string? value, int alignment = 0, string? format = null);
public void AppendFormatted(object? value, int alignment = 0, string? format = null);
}
}
```
We make a slight change to the rules for the meaning of an [_interpolated\_string\_expression_](https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#interpolated-strings):
**If the type of an interpolated string is `string` and the type `System.Runtime.CompilerServices.DefaultInterpolatedStringHandler` exists, and the current context supports using that type, the string**
**is lowered using the handler pattern. The final `string` value is then obtained by calling `ToStringAndClear()` on the handler type.**
**Otherwise, if** the type of an interpolated string is `System.IFormattable` or `System.FormattableString` [the rest is unchanged]
The "and the current context supports using that type" rule is intentionally vague to give the compiler leeway in optimizing usage of this pattern. The handler type is likely to be a ref struct
type, and ref struct types are normally not permitted in async methods. For this particular case, the compiler would be allowed to make use the handler if none of the interpolation holes contain
an `await` expression, as we can statically determine that the handler type is safely used without additional complicated analysis because the handler will be dropped after the interpolated string
expression is evaluated.
**~~Open~~ Question**:
Do we want to instead just make the compiler know about `DefaultInterpolatedStringHandler` and skip the `string.Format` call entirely? It would allow us to hide a method that we don't necessarily
want to put in people's faces when they manually call `string.Format`.
_Answer_: Yes.
**~~Open~~ Question**:
Do we want to have handlers for `System.IFormattable` and `System.FormattableString` as well?
_Answer_: No.
### Handler pattern codegen
In this section, method invocation resolution refers to the steps listed [here](https://github.com/dotnet/csharplang/blob/main/spec/expressions.md#method-invocations).
#### Constructor resolution
Given an _applicable\_interpolated\_string\_handler\_type_ `T` and an _interpolated\_string\_expression_ `i`, method invocation resolution and validation for a valid constructor on `T`
is performed as follows:
1. Member lookup for instance constructors is performed on `T`. The resulting method group is called `M`.
2. The argument list `A` is constructed as follows:
1. The first two arguments are integer constants, representing the literal length of `i`, and the number of _interpolation_ components in `i`, respectively.
2. If `i` is used as an argument to some parameter `pi` in method `M1`, and parameter `pi` is attributed with `System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute`,
then for every name `Argx` in the `Arguments` array of that attribute the compiler matches it to a parameter `px` that has the same name. The empty string is matched to the receiver
of `M1`.
* If any `Argx` is not able to be matched to a parameter of `M1`, or an `Argx` requests the receiver of `M1` and `M1` is a static method, an error is produced and no further
steps are taken.
* Otherwise, the type of every resolved `px` is added to the argument list, in the order specified by the `Arguments` array. Each `px` is passed with the same `ref` semantics as is specified in `M1`.
3. The final argument is a `bool`, passed as an `out` parameter.
3. Traditional method invocation resolution is performed with method group `M` and argument list `A`. For the purposes of method invocation final validation, the context of `M` is treated
as a _member\_access_ through type `T`.
* If a single-best constructor `F` was found, the result of overload resolution is `F`.
* If no applicable constructors were found, step 3 is retried, removing the final `bool` parameter from `A`. If this retry also finds no applicable members, an error is produced and
no further steps are taken.
* If no single-best method was found, the result of overload resolution is ambiguous, an error is produced, and no further steps are taken.
4. Final validation on `F` is performed.
* If any element of `A` occurred lexically after `i`, an error is produced and no further steps are taken.
* If any `A` requests the receiver of `F`, and `F` is an indexer being used as an _initializer\_target_ in a _member\_initializer_, then an error is reported and no further steps are taken.
Note: the resolution here intentionally do _not_ use the actual expressions passed as other arguments for `Argx` elements. We only consider the types post-conversion. This makes sure that we
don't have double-conversion issues, or unexpected cases where a lambda is bound to one delegate type when passed to `M1` and bound to a different delegate type when passed to `M`.
Note: We report an error for indexers uses as member initializers because of the order of evaluation for nested member initializers. Consider this code snippet:
```cs
var x1 = new C1 { C2 = { [GetString()] = { A = 2, B = 4 } } };
/* Lowering:
__c1 = new C1();
string argTemp = GetString();
__c1.C2[argTemp][1] = 2;
__c1.C2[argTemp][3] = 4;
Prints:
GetString
get_C2
get_C2
*/
string GetString()
{
Console.WriteLine("GetString");
return "";
}
class C1
{
private C2 c2 = new C2();
public C2 C2 { get { Console.WriteLine("get_C2"); return c2; } set { } }
}
class C2
{
public C3 this[string s]
{
get => new C3();
set { }
}
}
class C3
{
public int A
{
get => 0;
set { }
}
public int B
{
get => 0;
set { }
}
}
```
The arguments to `__c1.C2[]` are evaluated _before_ the receiver of the indexer. While we could come up with a lowering that works for this scenario (either by creating a temp for `__c1.C2`
and sharing it across both indexer invocations, or only using it for the first indexer invocation and sharing the argument across both invocations) we think that any lowering would be
confusing for what we believe is a pathological scenario. Therefore, we forbid the scenario entirely.
**~~Open Question~~**:
If we use a constructor instead of `Create`, we'd improve runtime codegen, at the expense of narrowing the pattern a bit.
_Answer_: We will restrict to constructors for now. We can revisit adding a general `Create` method later if the scenario arises.
#### `Append...` method overload resolution
Given an _applicable\_interpolated\_string\_handler\_type_ `T` and an _interpolated\_string\_expression_ `i`, overload resolution for a set of valid `Append...` methods on `T` is
performed as follows:
1. If there are any _interpolated\_regular\_string\_character_ components in `i`:
1. Member lookup on `T` with the name `AppendLiteral` is performed. The resulting method group is called `Ml`.
2. The argument list `Al` is constructed with one value parameter of type `string`.
3. Traditional method invocation resolution is performed with method group `Ml` and argument list `Al`. For the purposes of method invocation final validation, the context of `Ml`
is treated as a _member\_access_ through an instance of `T`.
* If a single-best method `Fi` is found and no errors were produced, the result of method invocation resolution is `Fi`.
* Otherwise, an error is reported.
2. For every _interpolation_ `ix` component of `i`:
1. Member lookup on `T` with the name `AppendFormatted` is performed. The resulting method group is called `Mf`.
2. The argument list `Af` is constructed:
1. The first parameter is the `expression` of `ix`, passed by value.
2. If `ix` directly contains a _constant\_expression_ component, then an integer value parameter is added, with the name `alignment` specified.
3. If `ix` is directly followed by an _interpolation\_format_, then a string value parameter is added, with the name `format` specified.
3. Traditional method invocation resolution is performed with method group `Mf` and argument list `Af`. For the purposes of method invocation final validation, the context of `Mf`
is treated as a _member\_access_ through an instance of `T`.
* If a single-best method `Fi` is found, the result of method invocation resolution is `Fi`.
* Otherwise, an error is reported.
3. Finally, for every `Fi` discovered in steps 1 and 2, final validation is performed:
* If any `Fi` does not return `bool` by value or `void`, an error is reported.
* If all `Fi` do not return the same type, an error is reported.
Note that these rules do not permit extension methods for the `Append...` calls. We could consider enabling that if we choose, but this is analogous to the enumerator
pattern, where we allow `GetEnumerator` to be an extension method, but not `Current` or `MoveNext()`.
These rules _do_ permit default parameters for the `Append...` calls, which will work with things like `CallerLineNumber` or `CallerArgumentExpression` (when supported by
the language).
We have separate overload lookup rules for base elements vs interpolation holes because some handlers will want to be able to understand the difference between the components
that were interpolated and the components that were part of the base string.
**~~Open~~ Question**
Some scenarios, like structured logging, want to be able to provide names for interpolation elements. For example, today a logging call might look like
`Log("{name} bought {itemCount} items", name, items.Count);`. The names inside the `{}` provide important structure information for loggers that help with ensuring output
is consistent and uniform. Some cases might be able to reuse the `:format` component of an interpolation hole for this, but many loggers already understand format specifiers
and have existing behavior for output formatting based on this info. Is there some syntax we can use to enable putting these named specifiers in?
Some cases may be able to get away with `CallerArgumentExpression`, provided that support does land in C# 10. But for cases that invoke a method/property, that may not be
sufficient.
_Answer_:
While there are some interesting parts to templated strings we could explore in an orthogonal language feature, we don't think a specific syntax here has much benefit over
solutions such as using a tuple: `$"{("StructuredCategory", myExpression)}"`.
#### Performing the conversion
Given an _applicable\_interpolated\_string\_handler\_type_ `T` and an _interpolated\_string\_expression_ `i` that had a valid constructor `Fc` and `Append...` methods `Fa` resolved,
lowering for `i` is performed as follows:
1. Any arguments to `Fc` that occur lexically before `i` are evaluated and stored into temporary variables in lexical order. In order to preserve lexical ordering, if `i` occurred as part
of a larger expression `e`, any components of `e` that occurred before `i` will be evaluated as well, again in lexical order.
2. `Fc` is called with the length of the interpolated string literal components, the number of _interpolation_ holes, any previously evaluated arguments, and a `bool` out argument
(if `Fc` was resolved with one as the last parameter). The result is stored into a temporary value `ib`.
1. The length of the literal components is calculated after replacing any _open_brace_escape_sequence_ with a single `{`, and any _close_brace_escape_sequence_
with a single `}`.
3. If `Fc` ended with a `bool` out argument, a check on that `bool` value is generated. If true, the methods in `Fa` will be called. Otherwise, they will not be called.
4. For every `Fax` in `Fa`, `Fax` is called on `ib` with either the current literal component or _interpolation_ expression, as appropriate. If `Fax` returns a `bool`, the result is
logically anded with all preceeding `Fax` calls.
1. If `Fax` is a call to `AppendLiteral`, the literal component is unescaped by replacing any _open_brace_escape_sequence_ with a single `{`, and any _close_brace_escape_sequence_
with a single `}`.
5. The result of the conversion is `ib`.
Again, note that arguments passed to `Fc` and arguments passed to `e` are the same temp. Conversions may occur on top of the temp to convert to a form that `Fc` requires, but for example
lambdas cannot be bound to a different delegate type between `Fc` and `e`.
**~~Open~~ Question**
This lowering means that subsequent parts of the interpolated string after a false-returning `Append...` call don't get evaluated. This could potentially be very confusing, particularly
if the format hole is side-effecting. We could instead evaluate all format holes first, then repeatedly call `Append...` with the results, stopping if it returns false. This would ensure
that all expressions get evaluated as one might expect, but we call as few methods as we need to. While the partial evaluation might be desirable for some more advanced cases, it is perhaps
non-intuitive for the general case.
Another alternative, if we want to always evaluate all format holes, is to remove the `Append...` version of the API and just do repeated `Format` calls. The handler can track whether it
should just be dropping the argument and immediately returning for this version.
_Answer_: We will have conditional evaluation of the holes.
**~~Open~~ Question**
Do we need to dispose of disposable handler types, and wrap calls with try/finally to ensure that Dispose is called? For example, the interpolated string handler in the bcl might have a
rented array inside it, and if one of the interpolation holes throws an exception during evaluation, that rented array could be leaked if it wasn't disposed.
_Answer_: No. handlers can be assigned to locals (such as `MyHandler handler = $"{MyCode()};`), and the lifetime of such handlers is unclear. Unlike foreach enumerators, where the lifetime
is obvious and no user-defined local is created for the enumerator.
## Other considerations
### Allow `string` types to be convertible to handlers as well
For type author simplicity, we could consider allowing expressions of type `string` to be implicitly-convertible to _applicable\_interpolated\_string\_handler\_types_. As proposed today,
authors will likely need to overload on both that handler type and regular `string` types, so their users don't have to understand the difference. This may be an annoying and non-obvious
overhead, as a `string` expression can be viewed as an interpolation with `expression.Length` prefilled length and 0 holes to be filled.
This would allow new APIs to only expose a handler, without also having to expose a `string`-accepting overload. However, it won't get around the need for changes to better conversion from
expression, so while it would work it may be unnecessary overhead.
_Answer_:
We think that this could end up being confusing, and there's an easy workaround for custom handler types: add a user-defined conversion from string.
### Incorporating spans for heap-less strings
`ValueStringBuilder` as it exists today has 2 constructors: one that takes a count, and allocates on the heap eagerly, and one that takes a `Span<char>`. That `Span<char>` is usually
a fixed size in the runtime codebase, around 250 elements on average. To truly replace that type, we should consider an extension to this where we also recognize `GetInterpolatedString`
methods that take a `Span<char>`, instead of just the count version. However, we see a few potential thorny cases to resolve here:
* We don't want to stackalloc repeatedly in a hot loop. If we were to do this extension to the feature, we'd likely want to share the stackalloc'd span between loop
iterations. We know this is safe, as `Span<T>` is a ref struct that can't be stored on the heap, and users would have to be pretty devious to manage to extract a
reference to that `Span` (such as creating a method that accepts such a handler then deliberately retrieving the `Span` from the handler and returning it to the
caller). However, allocating ahead of time produces other questions:
* Should we eagerly stackalloc? What if the loop is never entered, or exits before it needs the space?
* If we don't eagerly stackalloc, does that mean we introduce a hidden branch on every loop? Most loops likely won't care about this, but it could affect some tight loops that don't
want to pay the cost.
* Some strings can be quite big, and the appropriate amount to `stackalloc` is dependent on a number of factors, including runtime factors. We don't really want the C# compiler and
specification to have to determine this ahead of time, so we'd want to resolve https://github.com/dotnet/runtime/issues/25423 and add an API for the compiler to call in these cases. It
also adds more pros and cons to the points from the previous loop, where we don't want to potentially allocate large arrays on the heap many times or before one is needed.
_Answer_:
This is out of scope for C# 10. We can look at this in general when we look at the more general `params Span<T>` feature.
### Non-try version of the API
For simplicity, this spec currently just proposes recognizing a `Append...` method, and things that always succeed (like `InterpolatedStringHandler`) would always return true from the method.
This was done to support partial formatting scenarios where the user wants to stop formatting if an error occurs or if it's unnecessary, such as the logging case, but could potentially
introduce a bunch of unnecessary branches in standard interpolated string usage. We could consider an addendum where we use just `FormatX` methods if no `Append...` method is present, but
it does present questions about what we do if there's a mix of both `Append...` and `FormatX` calls.
_Answer_:
We want the non-try version of the API. The proposal has been updated to reflect this.
### Passing previous arguments to the handler
There is unfortunate lack of symmetry in the proposal at it currently exists: invoking an extension method in reduced form produces different semantics than invoking the extension method in
normal form. This is different from most other locations in the language, where reduced form is just a sugar. We propose adding an attribute to the framework that we will recognize when
binding a method, that informs the compiler that certain parameters should be passed to the constructor on the handler. Usage looks like this:
```cs
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class InterpolatedStringHandlerArgumentAttribute : Attribute
{
public InterpolatedStringHandlerArgumentAttribute(string argument);
public InterpolatedStringHandlerArgumentAttribute(params string[] arguments);
public string[] Arguments { get; }
}
}
```
Usage of this is then:
```cs
namespace System
{
public sealed class String
{
public static string Format(IFormatProvider? provider, [InterpolatedStringHandlerArgument("provider")] ref DefaultInterpolatedStringHandler handler);
}
}
namespace System.Runtime.CompilerServices
{
public ref struct DefaultInterpolatedStringHandler
{
public DefaultInterpolatedStringHandler(int baseLength, int holeCount, IFormatProvider? provider); // additional factory
}
}
var formatted = string.Format(CultureInfo.InvariantCulture, $"{X} = {Y}");
// Is lowered to
var tmp1 = CultureInfo.InvariantCulture;
var handler = new DefaultInterpolatedStringHandler(3, 2, tmp1);
handler.AppendFormatted(X);
handler.AppendLiteral(" = ");
handler.AppendFormatted(Y);
var formatted = string.Format(tmp1, handler);
```
The questions we need to answer:
1. Do we like this pattern in general?
2. Do we want to allow these arguments to come from after the handler parameter? Some existing patterns in the BCL, such as `Utf8Formatter`, put the value to be formatted _before_ the thing
needed to format into. To fit in best with these patterns, we'd likely want to allow this, but we need to decide if this out-of-order evaluate is ok.
_Answer_:
We want to support this. The spec has been updated to reflect this. Arguments will be required to be specified in lexical order at the call site, and if a needed argument to the create method
is specified after the interpolated string literal, an error is produced.
### `await` usage in interpolation holes
Because `$"{await A()}"` is a valid expression today, we need to rationalize how interpolation holes with await. We could solve this with a few rules:
1. If an interpolated string used as a `string`, `IFormattable`, or `FormattableString` has an `await` in an interpolation hole, fall back to old-style formatter.
2. If an interpolated string is subject to an _implicit\_string\_handler\_conversion_ and _applicable\_interpolated\_string\_handler\_type_ is a `ref struct`, `await` is not allowed to be used
in the format holes.
Fundamentally, this desugaring could use a ref struct in an async method as long as we guarantee that the `ref struct` will not need to be saved to the heap, which should be possible if we forbid
`await`s in the interpolation holes.
Alternatively, we could simply make all handler types non-ref structs, including the framework handler for interpolated strings. This would, however, preclude us from someday recognizing a `Span`
version that does not need to allocate any scratch space at all.
_Answer_:
We will treat interpolated string handlers the same as any other type: this means that if the handler type is a ref struct and the current context doesn't allow the usage of ref structs, it is
illegal to use handler here. The spec around lowering of string literals used as strings is intentionally vague to allow the compiler to decide on what rules it deems appropriate, but for custom
handler types they will have to follow the same rules as the rest of the language.
### Handlers as ref parameters
Some handlers might want to be passed as ref parameters (either `in` or `ref`). Should we allow either? And if so, what will a `ref` handler look like? `ref $""` is confusing, as you're not actually
passing the string by ref, you're passing the handler that is created from the ref by ref, and has similar potential issues with async methods.
_Answer_:
We want to support this. The spec has been updated to reflect this. The rules should reflect the same rules that apply to extension methods on value types.
### Interpolated strings through binary expressions and conversions
Because this proposal makes interpolated strings context sensitive, we would like to allow the compiler to treat a binary expression composed entirely of interpolated strings,
or an interpolated string subjected to a cast, as an interpolated string literal for the purposes of overload resolution. For example, take the following scenario:
```cs
struct Handler1
{
public Handler1(int literalLength, int formattedCount, C c) => ...;
// AppendX... methods as necessary
}
struct Handler2
{
public Handler2(int literalLength, int formattedCount, C c) => ...;
// AppendX... methods as necessary
}
class C
{
void M(Handler1 handler) => ...;
void M(Handler2 handler) => ...;
}
c.M($"{X}"); // Ambiguous between the M overloads
```
This would be ambiguous, necessitating a cast to either `Handler1` or `Handler2` in order to resolve. However, in making that cast, we would potentially throw away the information
that there is context from the method receiver, meaning that the cast would fail because there is nothing to fill in the information of `c`. A similar issue arises with binary concatenation
of strings: the user could want to format the literal across several lines to avoid line wrapping, but would not be able to because that would no longer be an interpolated string literal
convertible to the handler type.
To resolve these cases, we make the following changes:
* An _additive\_expression_ composed entirely of _interpolated\_string\_expressions_ and using only `+` operators is considered to be an _interpolated\_string\_literal_ for the purposes of
conversions and overload resolution. The final interpolated string is created by logically concatinating all individual _interpolated\_string\_expression_ components, from left to right.
* A _cast\_expression_ or a _relational\_expression_ with operator `as` whose operand is an _interpolated\_string\_expressions_ is considered an _interpolated\_string\_expressions_ for the
purposes of conversions and overload resolution.
**Open Questions**:
Do we want to do this? We don't do this for `System.FormattableString`, for example, but that can be broken out onto a different line, whereas this can be context-dependent and therefore not
able to be broken out into a different line. There are also no overload resolution concerns with `FormattableString` and `IFormattable`.
_Answer_:
We think that this is a valid use case for additive expressions, but that the cast version is not compelling enough at this time. We can add it later if necessary. The spec has been updated to
reflect this decision.
## Other use cases
See https://github.com/dotnet/runtime/issues/50635 for examples of proposed handler APIs using this pattern.

View file

@ -0,0 +1,320 @@
# Lambda improvements
## Summary
Proposed changes:
1. Allow lambdas with attributes
2. Allow lambdas with explicit return type
3. Infer a natural delegate type for lambdas and method groups
## Motivation
Support for attributes on lambdas would provide parity with methods and local functions.
Support for explicit return types would provide symmetry with lambda parameters where explicit types can be specified.
Allowing explicit return types would also provide control over compiler performance in nested lambdas where overload resolution must bind the lambda body currently to determine the signature.
A natural type for lambda expressions and method groups will allow more scenarios where lambdas and method groups may be used without an explicit delegate type, including as initializers in `var` declarations.
Requiring explicit delegate types for lambdas and method groups has been a friction point for customers, and has become an impediment to progress in ASP.NET with recent work on [MapAction](https://github.com/dotnet/aspnetcore/pull/29878).
[ASP.NET MapAction](https://github.com/dotnet/aspnetcore/pull/29878) without proposed changes (`MapAction()` takes a `System.Delegate` argument):
```csharp
[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name");
app.MapAction((Func<Todo>)GetTodo);
[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo;
app.MapAction((Func<Todo, Todo>)PostTodo);
```
[ASP.NET MapAction](https://github.com/dotnet/aspnetcore/pull/29878) with natural types for method groups:
```csharp
[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name");
app.MapAction(GetTodo);
[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo);
app.MapAction(PostTodo);
```
[ASP.NET MapAction](https://github.com/dotnet/aspnetcore/pull/29878) with attributes and natural types for lambda expressions:
```csharp
app.MapAction([HttpGet("/")] () => new Todo(Id: 0, Name: "Name"));
app.MapAction([HttpPost("/")] ([FromBody] Todo todo) => todo);
```
## Attributes
Attributes may be added to lambda expressions and lambda parameters.
To avoid ambiguity between method attributes and parameter attributes, a lambda expression with attributes must use a parenthesized parameter list.
Parameter types are not required.
```csharp
f = [A] () => { }; // [A] lambda
f = [return:A] x => x; // syntax error at '=>'
f = [return:A] (x) => x; // [A] lambda
f = [A] static x => x; // syntax error at '=>'
f = ([A] x) => x; // [A] x
f = ([A] ref int x) => x; // [A] x
```
Multiple attributes may be specified, either comma-separated within the same attribute list or as separate attribute lists.
```csharp
var f = [A1, A2][A3] () => { }; // ok
var g = ([A1][A2, A3] int x) => x; // ok
```
Attributes are not supported for _anonymous methods_ declared with `delegate { }` syntax.
```csharp
f = [A] delegate { return 1; }; // syntax error at 'delegate'
f = delegate ([A] int x) { return x; }; // syntax error at '['
```
The parser will look ahead to differentiate a collection initializer with an element assignment from a collection initializer with a lambda expression.
```csharp
var y = new C { [A] = x }; // ok: y[A] = x
var z = new C { [A] x => x }; // ok: z[0] = [A] x => x
```
The parser will treat `?[` as the start of a conditional element access.
```csharp
x = b ? [A]; // ok
y = b ? [A] () => { } : z; // syntax error at '('
```
Attributes on the lambda expression or lambda parameters will be emitted to metadata on the method that maps to the lambda.
In general, customers should not depend on how lambda expressions and local functions map from source to metadata. How lambdas and local functions are emitted can, and has, changed between compiler versions.
The changes proposed here are targeted at the `Delegate` driven scenario.
It should be valid to inspect the `MethodInfo` associated with a `Delegate` instance to determine the signature of the lambda expression or local function including any explicit attributes and additional metadata emitted by the compiler such as default parameters.
This allows teams such as ASP.NET to make available the same behaviors for lambdas and local functions as ordinary methods.
## Explicit return type
An explicit return type may be specified before the parenthesized parameter list.
```csharp
f = T () => default; // ok
f = short x => 1; // syntax error at '=>'
f = ref int (ref int x) => ref x; // ok
f = static void (_) => { }; // ok
f = async async (async async) => async; // ok?
```
The parser will look ahead to differentiate a method call `T()` from a lambda expression `T () => e`.
Explicit return types are not supported for anonymous methods declared with `delegate { }` syntax.
```csharp
f = delegate int { return 1; }; // syntax error
f = delegate int (int x) { return x; }; // syntax error
```
Method type inference should make an exact inference from an explicit lambda return type.
```csharp
static void F<T>(Func<T, T> f) { ... }
F(int (i) => i); // Func<int, int>
```
Variance conversions are not allowed from lambda return type to delegate return type (matching similar behavior for parameter types).
```csharp
Func<object> f1 = string () => null; // error
Func<object?> f2 = object () => x; // warning
```
The parser allows lambda expressions with `ref` return types within expressions without additional parentheses.
```csharp
d = ref int () => x; // d = (ref int () => x)
F(ref int () => x); // F((ref int () => x))
```
`var` cannot be used as an explicit return type for lambda expressions.
```csharp
class var { }
d = var (var v) => v; // error: contextual keyword 'var' cannot be used as explicit lambda return type
d = @var (var v) => v; // ok
d = ref var (ref var v) => ref v; // error: contextual keyword 'var' cannot be used as explicit lambda return type
d = ref @var (ref var v) => ref v; // ok
```
## Natural (function) type
An [_anonymous function_ expression](../../spec/expressions.md#anonymous-function-expressions) (a _lambda expression_ or an _anonymous method_) has a natural type if the parameters types are explicit and the return type is either explicit or can be inferred (see [inferred return type](../../spec/expressions.md#inferred-return-type)).
A _method group_ has a natural type if all candidate methods in the method group have a common signature. (If the method group may include extension methods, the candidates include the containing type and all extension method scopes.)
The natural type of an anonymous function expression or method group is a _function_type_.
A _function_type_ represents a method signature: the parameter types and ref kinds, and return type and ref kind.
Anonymous function expressions or method groups with the same signature have the same _function_type_.
_Function_types_ are used in a few specific contexts only:
- implicit and explicit conversions
- [method type inference](../../spec/expressions.md#type-inference) and [best common type](../../spec/expressions.md#finding-the-best-common-type-of-a-set-of-expressions)
- `var` initializers
A _function_type_ exists at compile time only: _function_types_ do not appear in source or metadata.
### Conversions
From a _function_type_ `F` there are implicit _function_type_ conversions:
- To a _function_type_ `G` if the parameters and return types of `F` are variance-convertible to the parameters and return type of `G`
- To `System.MulticastDelegate` or base classes or interfaces of `System.MulticastDelegate`
- To `System.Linq.Expressions.Expression` or `System.Linq.Expressions.LambdaExpression`
Anonymous function expressions and method groups already have _conversions from expression_ to delegate types and expression tree types (see [anonymous function conversions](../../spec/conversions.md#anonymous-function-conversions) and [method group conversions](../../spec/conversions.md#method-group-conversions)). Those conversions are sufficient for converting to strongly-typed delegate types and expression tree types. The _function_type_ conversions above add _conversions from type_ to the base types only: `System.MulticastDelegate`, `System.Linq.Expressions.Expression`, etc.
There are no conversions to a _function_type_ from a type other than a _function_type_.
There are no explicit conversions for _function_types_ since _function_types_ cannot be referenced in source.
A conversion to `System.MulticastDelegate` or base type or interface realizes the anonymous function or method group as an instance of an appropriate delegate type.
A conversion to `System.Linq.Expressions.Expression<TDelegate>` or base type realizes the lambda expression as an expression tree with an appropriate delegate type.
```csharp
Delegate d = delegate (object obj) { }; // Action<object>
Expression e = () => ""; // Expression<Func<string>>
object o = "".Clone; // Func<object>
```
_Function_type_ conversions are not implicit or explicit [standard conversions](../../spec/conversions.md#standard-conversions) and are not considered when determining whether a user-defined conversion operator is applicable to an anonymous function or method group.
From [evaluation of user defined conversions](../../spec/conversions.md#evaluation-of-user-defined-conversions):
> For a conversion operator to be applicable, it must be possible to perform a standard conversion ([Standard conversions](../../spec/conversions.md#standard-conversions)) from the source type to the operand type of the operator, and it must be possible to perform a standard conversion from the result type of the operator to the target type.
```csharp
class C
{
public static implicit operator C(Delegate d) { ... }
}
C c;
c = () => 1; // error: cannot convert lambda expression to type 'C'
c = (C)(() => 2); // error: cannot convert lambda expression to type 'C'
```
A warning is reported for an implicit conversion of a method group to `object`, since the conversion is valid but perhaps unintentional.
```csharp
Random r = new Random();
object obj;
obj = r.NextDouble; // warning: Converting method group to 'object'. Did you intend to invoke the method?
obj = (object)r.NextDouble; // ok
```
### Type inference
The existing rules for type inference are mostly unchanged (see [type inference](../../spec/expressions.md#type-inference)). There are however a **couple of changes** below to specific phases of type inference.
#### First phase
The [first phase](../../spec/expressions.md#the-first-phase) allows an anonymous function to bind to `Ti` even if `Ti` is not a delegate or expression tree type (perhaps a type parameter constrained to `System.Delegate` for instance).
> For each of the method arguments `Ei`:
>
> * If `Ei` is an anonymous function **and `Ti` is a delegate type or expression tree type**, an *explicit parameter type inference* is made from `Ei` to `Ti` **and an *explicit return type inference* is made from `Ei` to `Ti`.**
> * Otherwise, if `Ei` has a type `U` and `xi` is a value parameter then a *lower-bound inference* is made *from* `U` *to* `Ti`.
> * Otherwise, if `Ei` has a type `U` and `xi` is a `ref` or `out` parameter then an *exact inference* is made *from* `U` *to* `Ti`.
> * Otherwise, no inference is made for this argument.
> #### **Explicit return type inference**
>
> **An *explicit return type inference* is made *from* an expression `E` *to* a type `T` in the following way:**
>
> * **If `E` is an anonymous function with explicit return type `Ur` and `T` is a delegate type or expression tree type with return type `Vr` then an *exact inference* ([Exact inferences](../../spec/expressions.md#exact-inferences)) is made *from* `Ur` *to* `Vr`.**
#### Fixing
[Fixing](../../spec/expressions.md#fixing) ensures other conversions are preferred over _function_type_ conversions. (Lambda expressions and method group expressions only contribute to lower bounds so handling of _function_types_ is needed for lower bounds only.)
> An *unfixed* type variable `Xi` with a set of bounds is *fixed* as follows:
>
> * The set of *candidate types* `Uj` starts out as the set of all types in the set of bounds for `Xi` **where function types are ignored in lower bounds if there any types that are not function types**.
> * We then examine each bound for `Xi` in turn: For each exact bound `U` of `Xi` all types `Uj` which are not identical to `U` are removed from the candidate set. For each lower bound `U` of `Xi` all types `Uj` to which there is *not* an implicit conversion from `U` are removed from the candidate set. For each upper bound `U` of `Xi` all types `Uj` from which there is *not* an implicit conversion to `U` are removed from the candidate set.
> * If among the remaining candidate types `Uj` there is a unique type `V` from which there is an implicit conversion to all the other candidate types, then `Xi` is fixed to `V`.
> * Otherwise, type inference fails.
### Best common type
[Best common type](../../spec/expressions.md#finding-the-best-common-type-of-a-set-of-expressions) is defined in terms of type inference so the type inference changes above apply to best common type as well.
```csharp
var fs = new[] { (string s) => s.Length; (string s) => int.Parse(s) } // Func<string, int>[]
```
### `var`
Anonymous functions and method groups with function types can be used as initializers in `var` declarations.
```csharp
var f1 = () => default; // error: cannot infer type
var f2 = x => x; // error: cannot infer type
var f3 = () => 1; // System.Func<int>
var f4 = string () => null; // System.Func<string>
var f5 = delegate (object o) { }; // System.Action<object>
static void F1() { }
static void F1<T>(this T t) { }
static void F2(this string s) { }
var f6 = F1; // error: multiple methods
var f7 = "".F1; // System.Action
var f8 = F2; // System.Action<string>
```
Function types are not used in assignments to discards.
```csharp
d = () => 0; // ok
_ = () => 1; // error
```
### Delegate types
The delegate type for the anonymous function or method group with parameter types `P1, ..., Pn` and return type `R` is:
- if any parameter or return value is not by value, or there are more than 16 parameters, or any of the parameter types or return are not valid type arguments (say, `(int* p) => { }`), then the delegate is a synthesized `internal` anonymous delegate type with signature that matches the anonymous function or method group, and with parameter names `arg1, ..., argn` or `arg` if a single parameter;
- if `R` is `void`, then the delegate type is `System.Action<P1, ..., Pn>`;
- otherwise the delegate type is `System.Func<P1, ..., Pn, R>`.
The compiler may allow more signatures to bind to `System.Action<>` and `System.Func<>` types in the future (if `ref struct` types are allowed type arguments for instance).
`modopt()` or `modreq()` in the method group signature are ignored in the corresponding delegate type.
If two anonymous functions or method groups in the same compilation require synthesized delegate types with the same parameter types and modifiers and the same return type and modifiers, the compiler will use the same synthesized delegate type.
### Overload resolution
[Better function member](../../spec/expressions.md#better-function-member) is updated to prefer members where none of the conversions and none of the type arguments involved inferred types from lambda expressions or method groups.
> #### Better function member
> ...
> Given an argument list `A` with a set of argument expressions `{E1, E2, ..., En}` and two applicable function members `Mp` and `Mq` with parameter types `{P1, P2, ..., Pn}` and `{Q1, Q2, ..., Qn}`, `Mp` is defined to be a ***better function member*** than `Mq` if
>
> 1. **for each argument, the implicit conversion from `Ex` to `Px` is not a _function_type_conversion_, and**
> * **`Mp` is a non-generic method or `Mp` is a generic method with type parameters `{X1, X2, ..., Xp}` and for each type parameter `Xi` the type argument is inferred from an expression or from a type other than a _function_type_, and**
> * **for at least one argument, the implicit conversion from `Ex` to `Qx` is a _function_type_conversion_, or `Mq` is a generic method with type parameters `{Y1, Y2, ..., Yq}` and for at least one type parameter `Yi` the type argument is inferred from a _function_type_, or**
> 2. for each argument, the implicit conversion from `Ex` to `Qx` is not better than the implicit conversion from `Ex` to `Px`, and for at least one argument, the conversion from `Ex` to `Px` is better than the conversion from `Ex` to `Qx`.
[Better conversion from expression](../../spec/expressions.md#better-conversion-from-expression) is updated to prefer conversions that did not involve inferred types from lambda expressions or method groups.
> #### Better conversion from expression
>
> Given an implicit conversion `C1` that converts from an expression `E` to a type `T1`, and an implicit conversion `C2` that converts from an expression `E` to a type `T2`, `C1` is a ***better conversion*** than `C2` if:
> 1. **`C1` is not a _function_type_conversion_ and `C2` is a _function_type_conversion_, or**
> 2. `E` is a non-constant _interpolated\_string\_expression_, `C1` is an _implicit\_string\_handler\_conversion_, `T1` is an _applicable\_interpolated\_string\_handler\_type_, and `C2` is not an _implicit\_string\_handler\_conversion_, or
> 3. `E` does not exactly match `T2` and at least one of the following holds:
> * `E` exactly matches `T1` ([Exactly matching Expression](../../spec/expressions.md#exactly-matching-expression))
> * `T1` is a better conversion target than `T2` ([Better conversion target](../../spec/expressions.md#better-conversion-target))
## Syntax
```antlr
lambda_expression
: modifier* identifier '=>' (block | expression)
| attribute_list* modifier* type? lambda_parameters '=>' (block | expression)
;
lambda_parameters
: lambda_parameter
| '(' (lambda_parameter (',' lambda_parameter)*)? ')'
;
lambda_parameter
: identifier
| attribute_list* modifier* type? identifier equals_value_clause?
;
```
## Open issues
Should default values be supported for lambda expression parameters for completeness?
Should `System.Diagnostics.ConditionalAttribute` be disallowed on lambda expressions since there are few scenarios where a lambda expression could be used conditionally?
```csharp
([Conditional("DEBUG")] static (x, y) => Assert(x == y))(a, b); // ok?
```
Should the _function_type_ be available from the compiler API, in addition to the resulting delegate type?
Currently, the inferred delegate type uses `System.Action<>` or `System.Func<>` when parameter and return types are valid type arguments _and_ there are no more than 16 parameters, and if the expected `Action<>` or `Func<>` type is missing, an error is reported. Instead, should the compiler use `System.Action<>` or `System.Func<>` regardless of arity? And if the expected type is missing, synthesize a delegate type otherwise?

View file

@ -0,0 +1,247 @@
# Parameterless struct constructors
## Summary
Support parameterless constructors and instance field initializers for struct types.
## Motivation
Explicit parameterless constructors would give more control over minimally constructed instances of the struct type.
Instance field initializers would allow simplified initialization across multiple constructors.
Together these would close an obvious gap between `struct` and `class` declarations.
Support for field initializers would also allow initialization of fields in `record struct` declarations without explicitly implementing the primary constructor.
```csharp
record struct Person(string Name)
{
public object Id { get; init; } = GetNextId();
}
```
If struct field initializers are supported for constructors with parameters, it seems natural to extend that to parameterless constructors as well.
```csharp
record struct Person()
{
public string Name { get; init; }
public object Id { get; init; } = GetNextId();
}
```
## Proposal
### Instance field initializers
Instance field declarations for a struct may include initializers.
As with [class field initializers](https://github.com/dotnet/csharplang/blob/main/spec/classes.md#instance-field-initialization):
> A variable initializer for an instance field cannot reference the instance being created.
### Constructors
A struct may declare a parameterless instance constructor.
A parameterless instance constructor is valid for all struct kinds including `struct`, `readonly struct`, `ref struct`, and `record struct`.
If the struct declaration does not contain any explicit instance constructors, and the struct has field initializers, the compiler will synthesize a `public` parameterless instance constructor.
The parameterless constructor may be synthesized even if all initializer values are zeros.
Otherwise, the struct (see [struct constructors](https://github.com/dotnet/csharplang/blob/main/spec/structs.md#constructors)) ...
> implicitly has a parameterless instance constructor which always returns the value that results from setting all value type fields to their default value and all reference type fields to null.
### Modifiers
A parameterless instance struct constructor must be declared `public`.
```csharp
struct S0 { } // ok
struct S1 { public S1() { } } // ok
struct S2 { internal S2() { } } // error: parameterless constructor must be 'public'
```
Non-public constructors are ignored when importing types from metadata.
Constructors can be declared `extern` or `unsafe`.
Constructors cannot be `partial`.
### Executing field initializers
Execution of struct instance field initializers matches execution of [class field initializers](https://github.com/dotnet/csharplang/blob/main/spec/classes.md#instance-variable-initializers) with **one qualifier**:
> When an instance constructor has no constructor initializer, **or when the constructor initializer `this()` represents the default parameterless constructor**, ... that constructor implicitly performs the initializations specified by the _variable_initializers_ of the instance fields ... . This corresponds to a sequence of assignments that are executed immediately upon entry to the constructor ... . The variable initializers are executed in the textual order in which they appear in the ... declaration.
### Definite assignment
Instance fields (other than `fixed` fields) must be definitely assigned in struct instance constructors that do not have a `this()` initializer (see [struct constructors](https://github.com/dotnet/csharplang/blob/main/spec/structs.md#constructors)).
Definite assignment of struct instance fields is required within synthesized and explicit parameterless constructors.
```csharp
struct S0 // ok: no synthesized constructor
{
int x;
object y;
}
struct S1
{
int x = 1;
object y; // error: field 'y' must be assigned
}
struct S2
{
int x = 1;
object y;
public S2() { } // error: field 'y' must be assigned
}
```
### No `base()` initializer
A `base()` initializer is disallowed in struct constructors.
The compiler will not emit a call to the base `System.ValueType` constructor from any struct instance constructors including explicit and synthesized parameterless constructors.
### `record struct`
If a `record struct` does not contain a primary constructor nor any instance constructors, and the `record struct` has field initializers, the compiler will synthesize a `public` parameterless instance constructor.
```csharp
record struct R0; // no parameterless .ctor
record struct R1 { int F = 42; } // synthesized .ctor: public R1() { F = 42; }
record struct R2(int F) { int F = F; } // no parameterless .ctor
```
A `record struct` with an empty parameter list will have a parameterless primary constructor.
```csharp
record struct R3(); // primary .ctor: public R3() { }
record struct R4() { int F = 42; } // primary .ctor: public R4() { F = 42; }
```
An explicit parameterless constructor in a `record struct` must call the primary constructor.
```csharp
record struct R5(int F)
{
public R5() { } // error: must call 'this(int F)'
public int F = F;
}
```
### Fields
The implicitly-defined parameterless constructor will zero fields rather than calling any parameterless constructors for the field types. No warnings are reported that field constructors are ignored.
_No change from C#9._
```csharp
struct S0
{
public S0() { }
}
struct S1
{
S0 F; // S0 constructor ignored
}
struct S<T> where T : struct
{
T F; // constructor (if any) ignored
}
```
### `default` expression
`default` ignores the parameterless constructor and generates a zeroed instance.
_No change from C#9._
```csharp
// struct S { public S() { } }
_ = default(S); // constructor ignored, no warning
```
### `new()`
Object creation invokes the parameterless constructor if public; otherwise the instance is zeroed.
_No change from C#9._
```csharp
// public struct PublicConstructor { public PublicConstructor() { } }
// public struct PrivateConstructor { private PrivateConstructor() { } }
_ = new PublicConstructor(); // call PublicConstructor::.ctor()
_ = new PrivateConstructor(); // initobj PrivateConstructor
```
A warning wave may report a warning for use of `new()` with a struct type that has constructors but no parameterless constructor.
No warning will be reported when using substituting such a struct type for a type parameter with a `new()` or `struct` constraint.
```csharp
struct S { public S(int i) { } }
static T CreateNew<T>() where T : new() => new T();
_ = new S(); // warning: no constructor called
_ = CreateNew<S>(); // ok
```
### Uninitialized values
A local or field of a struct type that is not explicitly initialized is zeroed.
The compiler reports a definite assignment error for an uninitialized struct that is not empty.
_No change from C#9._
```csharp
NoConstructor s1;
PublicConstructor s2;
s1.ToString(); // error: use of unassigned local (unless type is empty)
s2.ToString(); // error: use of unassigned local (unless type is empty)
```
### Array allocation
Array allocation ignores any parameterless constructor and generates zeroed elements.
_No change from C#9._
```csharp
// struct S { public S() { } }
var a = new S[1]; // constructor ignored, no warning
```
### Parameter default value `new()`
A parameter default value of `new()` binds to the parameterless constructor if public (and reports an error that the value is not constant); otherwise the instance is zeroed.
_No change from C#9._
```csharp
// public struct PublicConstructor { public PublicConstructor() { } }
// public struct PrivateConstructor { private PrivateConstructor() { } }
static void F1(PublicConstructor s1 = new()) { } // error: default value must be constant
static void F2(PrivateConstructor s2 = new()) { } // ok: initobj
```
### Type parameter constraints: `new()` and `struct`
The `new()` and `struct` type parameter constraints require the parameterless constructor to be `public` if defined (see [satisfying constraints](https://github.com/dotnet/csharplang/blob/main/spec/types.md#satisfying-constraints)).
The compiler assumes all structs satisfy `new()` and `struct` constraints.
_No change from C#9._
```csharp
// public struct PublicConstructor { public PublicConstructor() { } }
// public struct InternalConstructor { internal InternalConstructor() { } }
static T CreateNew<T>() where T : new() => new T();
static T CreateStruct<T>() where T : struct => new T();
_ = CreateNew<PublicConstructor>(); // ok
_ = CreateStruct<PublicConstructor>(); // ok
_ = CreateNew<InternalConstructor>(); // compiles; may fail at runtime
_ = CreateStruct<InternalConstructor>(); // compiles; may fail at runtime
```
`new T()` is emitted as a call to `System.Activator.CreateInstance<T>()`, and the compiler assumes the implementation of `CreateInstance<T>()` invokes the `public` parameterless constructor if defined.
_With .NET Framework, `Activator.CreateInstance<T>()` invokes the parameterless constructor if the constraint is `where T : new()` but appears to ignore the parameterless constructor if the constraint is `where T : struct`._
### Optional parameters
Constructors with optional parameters are not considered parameterless constructors.
_No change from C#9._
```csharp
struct S1 { public S1(string s = "") { } }
struct S2 { public S2(params object[] args) { } }
_ = new S1(); // ok: ignores constructor
_ = new S2(); // ok: ignores constructor
```
### Metadata
Explicit and synthesized parameterless struct instance constructors will be emitted to metadata.
Public parameterless struct instance constructors will be imported from metadata; non-public struct instance constructors will be ignored.
_No change from C#9._
## See also
- https://github.com/dotnet/roslyn/issues/1029
## Design meetings
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-28.md#open-questions-in-record-and-parameterless-structs
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-10.md#parameterless-struct-constructors
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-01-27.md#field-initializers

View file

@ -59,7 +59,7 @@ The method can be declared explicitly. It is an error if the explicit declaratio
If `Equals(R other)` is user-defined (not synthesized) but `GetHashCode` is not, a warning is produced.
```C#
public bool Equals(R other);
public readonly bool Equals(R other);
```
The synthesized `Equals(R)` returns `true` if and only if for each instance field `fieldN` in the record struct
@ -76,14 +76,14 @@ The `Equals` method called by the `==` operator is the `Equals(R other)` method
The record struct includes a synthesized override equivalent to a method declared as follows:
```C#
public override bool Equals(object? obj);
public override readonly bool Equals(object? obj);
```
It is an error if the override is declared explicitly.
The synthesized override returns `other is R temp && Equals(temp)` where `R` is the record struct.
The record struct includes a synthesized override equivalent to a method declared as follows:
```C#
public override int GetHashCode();
public override readonly int GetHashCode();
```
The method can be declared explicitly.
@ -135,6 +135,8 @@ The method does the following:
For a member that has a value type, we will convert its value to a string representation using the most efficient method available to the target platform. At present that means calling `ToString` before passing to `StringBuilder.Append`.
If the record's printable members do not include a readable property with a non-`readonly` `get` accessor, then the synthesized `PrintMembers` is `readonly`. There is no requirement for the record's fields to be `readonly` for the `PrintMembers` method to be `readonly`.
The `PrintMembers` method can be declared explicitly.
It is an error if the explicit declaration does not match the expected signature or accessibility.
@ -143,6 +145,8 @@ The record struct includes a synthesized method equivalent to a method declared
public override string ToString();
```
If the record struct's `PrintMembers` method is `readonly`, then the synthesized `ToString()` method is `readonly`.
The method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility.
The synthesized method:
@ -243,7 +247,9 @@ For a record struct:
Both kinds of set accessors (`set` and `init`) are considered "matching". So the user may declare an init-only property
in place of a synthesized mutable one.
An inherited `abstract` property with matching type is overridden.
No auto-property is created if the record struct has an instance field with expected name and type.
It is an error if the inherited property does not have `public` `get` and `set`/`init` accessors.
It is an error if the inherited property or field is hidden.
The auto-property is initialized to the value of the corresponding primary constructor parameter.
Attributes can be applied to the synthesized auto-property and its backing field by using `property:` or `field:`
targets for attributes syntactically applied to the corresponding record struct parameter.
@ -255,10 +261,12 @@ parameter declaration for each parameter of the primary constructor declaration.
of the Deconstruct method has the same type as the corresponding parameter of the primary
constructor declaration. The body of the method assigns each parameter of the Deconstruct method
to the value from an instance member access to a member of the same name.
If the instance members accessed in the body do not include a property with
a non-`readonly` `get` accessor, then the synthesized `Deconstruct` method is `readonly`.
The method can be declared explicitly. It is an error if the explicit declaration does not match
the expected signature or accessibility, or is static.
# Allow `with` expression on structs
## Allow `with` expression on structs
It is now valid for the receiver in a `with` expression to have a struct type.
@ -270,9 +278,9 @@ For a receiver with struct type, the receiver is first copied, then each `member
the same way as an assignment to a field or property access of the result of the conversion.
Assignments are processed in lexical order.
# Improvements on records
## Improvements on records
## Allow `record class`
### Allow `record class`
The existing syntax for record types allows `record class` with the same meaning as `record`:
@ -283,21 +291,13 @@ record_declaration
;
```
## Allow user-defined positional members to be fields
### Allow user-defined positional members to be fields
See https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-05.md#changing-the-member-type-of-a-primary-constructor-parameter
There is a back compat issue, so we may either drop this feature or we need to implement it fast (as a bug fix).
No auto-property is created if the record has or inherits an instance field with expected name and type.
```csharp
public record Base
{
public int Field;
}
public record Derived(int Field);
```
# Allow parameterless constructors and member initializers in structs
## Allow parameterless constructors and member initializers in structs
We are going to support both parameterless constructors and member initializers in structs.
This will be specified in more details.
@ -307,7 +307,7 @@ Allow parameterless ctors on structs and also field initializers (no runtime det
We will enumerate scenarios where initializers aren't evaluated: arrays, generics, default, ...
Consider diagnostics for using struct with parameterless ctor in some of those cases?
# Open questions
## Open questions
- should we disallow a user-defined constructor with a copy constructor signature?
- confirm that we want to disallow members named "Clone".
@ -318,7 +318,7 @@ Consider diagnostics for using struct with parameterless ctor in some of those c
- could field- or property-targeting attributes be placed in the positional parameter list?
- how to place attributes on the properties of a record struct? IDE has serialization types that would work nicely as record structs, but which need attributes on the members. Supporting `[property: DataMember(Order = 1)]` would solve this.
## Answered
### Answered
- confirm that we want to keep PrintMembers design (separate method returning `bool`) (answer: yes)
- confirm we won't allow `record ref struct` (issue with `IEquatable<RefStruct>` and ref fields) (answer: yes)

View file

@ -1,5 +1,5 @@
Async Task Types in C#
======================
# Async Task Types in C# #
Extend `async` to support _task types_ that match a specific pattern, in addition to the well known types
`System.Threading.Tasks.Task` and `System.Threading.Tasks.Task<T>`.
@ -60,9 +60,6 @@ The difference is, for those well known types, the _builder types_ are also know
`Builder.Create()` is invoked to create an instance of the _builder type_.
If the state machine is implemented as a `struct`, then `builder.SetStateMachine(stateMachine)` is called
with a boxed instance of the state machine that the builder can cache if necessary.
`builder.Start(ref stateMachine)` is invoked to associate the builder with compiler-generated state machine instance.
The builder must call `stateMachine.MoveNext()` either in `Start()` or after `Start()` has returned to advance the state machine.
After `Start()` returns, the `async` method calls `builder.Task` for the task to return from the async method.
@ -74,9 +71,13 @@ If an exception is thrown in the state machine, `builder.SetException(exception)
If the state machine reaches an `await expr` expression, `expr.GetAwaiter()` is invoked.
If the awaiter implements `ICriticalNotifyCompletion` and `IsCompleted` is false,
the state machine invokes `builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine)`.
`AwaitUnsafeOnCompleted()` should call `awaiter.OnCompleted(action)` with an action that calls `stateMachine.MoveNext()`
`AwaitUnsafeOnCompleted()` should call `awaiter.UnsafeOnCompleted(action)` with an `Action` that calls `stateMachine.MoveNext()`
when the awaiter completes. Similarly for `INotifyCompletion` and `builder.AwaitOnCompleted()`.
`SetStateMachine(IAsyncStateMachine)` is called by the compiler-generated `IAsyncStateMachine` implementation.
That can be used to identify the instance of the builder associated with a state machine instance, particularly for cases where the state machine is implemented as a value type:
if the builder calls `stateMachine.SetStateMachine(stateMachine)`, the `stateMachine` will call `builder.SetStateMachine(stateMachine)` on the _builder instance associated with `stateMachine`_.
## Overload Resolution
Overload resolution is extended to recognize _task types_ in addition to `Task` and `Task<T>`.

View file

@ -1,10 +1,5 @@
# Async Main
* [x] Proposed
* [ ] Prototype
* [ ] Implementation
* [ ] Specification
## Summary
[summary]: #summary

View file

@ -1,10 +1,5 @@
# pattern-matching with generics
* [x] Proposed
* [ ] Prototype:
* [ ] Implementation:
* [ ] Specification:
## Summary
[summary]: #summary

View file

@ -1,10 +1,5 @@
# Target-typed "default" literal
* [x] Proposed
* [x] Prototype
* [x] Implementation
* [ ] Specification
## Summary
[summary]: #summary

View file

@ -1,10 +1,5 @@
# private protected
* [x] Proposed
* [x] Prototype: [Complete](https://github.com/dotnet/roslyn/blob/master/docs/features/private-protected.md)
* [x] Implementation: [Complete](https://github.com/dotnet/roslyn/blob/master/docs/features/private-protected.md)
* [x] Specification: [Complete](#detailed-design)
## Summary
[summary]: #summary

View file

@ -1,10 +1,5 @@
# Readonly references
* [x] Proposed
* [x] Prototype
* [x] Implementation: Started
* [ ] Specification: Not Started
## Summary
[summary]: #summary

View file

@ -279,7 +279,7 @@ We wish to ensure that no `ref` local variable, and no variable of `ref struct`
- For an assignment `e1 = e2`, if the type of `e1` is a `ref struct` type, then the *safe-to-escape* of `e2` must be at least as wide a scope as the *safe-to-escape* of `e1`.
- For a method invocation if there is a `ref` or `out` argument of a `ref struct` type (including the receiver), with *safe-to-escape* E1, then no argument (including the receiver) may have a narrower *safe-to-escape* than E1. [Sample](#method-arguments-must-match)
- For a method invocation if there is a `ref` or `out` argument of a `ref struct` type (including the receiver unless the type is `readonly`), with *safe-to-escape* E1, then no argument (including the receiver) may have a narrower *safe-to-escape* than E1. [Sample](#method-arguments-must-match)
- A local function or anonymous function may not refer to a local or parameter of `ref struct` type declared in an enclosing scope.
@ -292,9 +292,9 @@ We wish to ensure that no `ref` local variable, and no variable of `ref struct`
These explanations and samples help explain why many of the safety rules above exist
### Method Arguments Must Match
When invoking a method where there is an `out`, `ref` parameter that is a `ref struct` including the receiver then all of the `ref struct` need to have the same lifetime. This is necessary because C# must make all of its decisions around lifetime safety based on the information available in the signature of the method and the lifetime of the values at the call site.
When invoking a method where there is an `out` or `ref` parameter that is a `ref struct` then all of the `ref struct` parameters need to have the same lifetime. This is necessary because C# must make all of its decisions around lifetime safety based on the information available in the signature of the method and the lifetime of the values at the call site.
When there are `ref` parameters that are `ref struct` then there is the possiblity they could swap around their contents. Hence at the call site we must ensure all of these **potential** swaps are compatible. If the language didn't enforce that then it will allow for bad code like the following.
When there are `ref` parameters that are `ref struct` then there is the potential that they could swap around their contents. Hence at the call site we must ensure all of these **potential** swaps are compatible. If the language didn't enforce that then it will allow for bad code like the following.
```csharp
void M1(ref Span<int> s1)
@ -311,7 +311,7 @@ void Swap(ref Span<int> x, ref Span<int> y)
}
```
The restriction on the receiver is necessary because while none of its contents are ref-safe-to-escape it can store the provided values. This means with mismatched lifetimes you could create a type safety hole in the following way:
This analysis of `ref` parameters includes the receiver in instance methods. This is necessary because it can be used to store values passed in as parameters, just as a `ref` parameter could`. This means with mismatched lifetimes you could create a type safety hole in the following way:
```csharp
ref struct S
@ -334,6 +334,8 @@ void Broken(ref S s)
}
```
For the purpose of this analysis the receiver is considered an `in`, not a `ref`, if the type is a `readonly struct`. In that case the receiver cannot be used to store values from other parameters, it is effectively an `in` parameter for analysis purposes. Hence the same example above is legal when `S` is `readonly` because the `span` cannot be stored anywhere.
### Struct This Escape
When it comes to span safety rules, the `this` value in an instance member is modeled as a parameter to the member. Now for a `struct` the type of `this` is actually `ref S` where in a `class` it's simply `S` (for members of a `class / struct` named S).

View file

@ -1,10 +1,5 @@
# Async Streams
* [x] Proposed
* [x] Prototype
* [ ] Implementation
* [ ] Specification
## Summary
[summary]: #summary

View file

@ -1,10 +1,5 @@
# default interface methods
* [x] Proposed
* [ ] Prototype: [In progress](https://github.com/dotnet/roslyn/blob/master/docs/features/DefaultInterfaceImplementation.md)
* [ ] Implementation: None
* [ ] Specification: In progress, below
## Summary
[summary]: #summary

View file

@ -1,10 +1,5 @@
# null coalescing assignment
* [x] Proposed
* [x] Prototype: Completed
* [x] Implementation: Completed
* [x] Specification: Below
## Summary
[summary]: #summary

View file

@ -154,6 +154,12 @@ The new indexer will be implemented by converting the argument of type `Index` i
- When the argument is of the form `^expr2` and the type of `expr2` is `int`, it will be translated to `receiver.Length - expr2`.
- Otherwise, it will be translated as `expr.GetOffset(receiver.Length)`.
Regardless of the specific conversion strategy, the order of evaluation should be equivalent to the following:
1. `receiver` is evaluated;
2. `expr` is evaluated;
3. `length` is evaluated, if needed;
4. the `int` based indexer is invoked.
This allows for developers to use the `Index` feature on existing types without the need for modification. For example:
``` csharp
@ -221,7 +227,13 @@ This value will be re-used in the calculation of the second `Slice` argument. Wh
- When `expr` is of the form `expr1..` (where `expr1` can be omitted), then it will be emitted as `receiver.Length - start`.
- Otherwise, it will be emitted as `expr.End.GetOffset(receiver.Length) - start`.
The `receiver`, `Length`, and `expr` expressions will be spilled as appropriate to ensure any side effects are only executed once. For example:
Regardless of the specific conversion strategy, the order of evaluation should be equivalent to the following:
1. `receiver` is evaluated;
2. `expr` is evaluated;
3. `length` is evaluated, if needed;
4. the `Slice` method is invoked.
The `receiver`, `expr`, and `length` expressions will be spilled as appropriate to ensure any side effects are only executed once. For example:
``` csharp
class Collection {
@ -249,7 +261,7 @@ class SideEffect {
void Use() {
var array = Get()[0..2];
Console.WriteLine(array.length);
Console.WriteLine(array.Length);
}
}
```
@ -259,7 +271,7 @@ This code will print "Get Length 2".
The language will special case the following known types:
- `string`: the method `Substring` will be used instead of `Slice`.
- `array`: the method `System.Reflection.CompilerServices.GetSubArray` will be used instead of `Slice`.
- `array`: the method `System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray` will be used instead of `Slice`.
## Alternatives

View file

@ -174,7 +174,7 @@ public static readonly event Event4
Some other syntax examples:
* Expression bodied members: `public readonly float ExpressionBodiedMember => (x * x) + (y * y);`
* Generic constraints: `public static readonly void GenericMethod<T>(T value) where T : struct { }`
* Generic constraints: `public readonly void GenericMethod<T>(T value) where T : struct { }`
The compiler would emit the instance member, as usual, and would additionally emit a compiler recognized attribute indicating that the instance member does not modify state. This effectively causes the hidden `this` parameter to become `in T` instead of `ref T`.

View file

@ -141,7 +141,7 @@ etc ... The code generation will be different only in that there will not be a c
// statements
}
finally {
if (resource != null) resource.Dispose();
if (r != null) r.Dispose();
}
}
```

View file

@ -1,5 +1,4 @@
Extending Partial Methods
=====
# Extending Partial Methods
## Summary
This proposal aims to remove all restrictions around the signatures of `partial`

View file

@ -1,5 +1,4 @@
Init Only Setters
=====
# Init Only Setters
## Summary
This proposal adds the concept of init only properties and indexers to C#.

View file

@ -1,10 +1,5 @@
# Module Initializers
* [x] Proposed
* [ ] Prototype: [In Progress](https://github.com/jnm2/roslyn/tree/module_initializer)
* [ ] Implementation: In Progress
* [ ] Specification: [Not Started]()
## Summary
[summary]: #summary

View file

@ -135,8 +135,6 @@ expr!.M();
_ = a?.b!.c;
```
The `primary_expression` and `null_conditional_operations_no_suppression` must be of a nullable type.
The postfix `!` operator has no runtime effect - it evaluates to the result of the underlying expression. Its only role is to change the null state of the expression to "not null", and to limit warnings given on its use.
### Nullable compiler directives
@ -498,7 +496,7 @@ Generic type inference is enhanced to help decide whether inferred reference typ
### The first phase
Nullable reference types flow into the bounds from the initial expressions, as described below. In addition, two new kinds of bounds, namely `null` and `default` are introduced. Their purpose is to carry through occurrences of `null` or `default` in the input expressions, which may cause an inferred type to be nullable, even when it otherwise wouldn't. This works even for nullable *value* types, which are enhanced to pick up "nullness" in the inference process.
Nullable reference types flow into the bounds from the initial expressions, as described below. In addition, two new kinds of bounds, namely `null` and `default` are introduced. Their purpose is to carry through occurrences of `null` or `default` in the input expressions, which may cause an inferred type to be nullable, even when it otherwise wouldn't.
The determination of what bounds to add in the first phase are enhanced as follows:
@ -533,7 +531,7 @@ To handle these we add more phases to fixing, which is now:
3. Eliminate candidates that do not have an implicit conversion to all the other candidates
4. If the remaining candidates do not all have identity conversions to one another, then type inference fails
5. *Merge* the remaining candidates as described below
6. If the resulting candidate is a reference type or a nonnullable value type and *all* of the exact bounds or *any* of the lower bounds are nullable value types, nullable reference types, `null` or `default`, then `?` is added to the resulting candidate, making it a nullable value type or reference type.
6. If the resulting candidate is a reference type and *all* of the exact bounds or *any* of the lower bounds are nullable reference types, `null` or `default`, then `?` is added to the resulting candidate, making it a nullable reference type.
*Merging* is described between two candidate types. It is transitive and commutative, so the candidates can be merged in any order with the same ultimate result. It is undefined if the two candidate types are not identity convertible to each other.

View file

@ -38,7 +38,7 @@ Records cannot inherit from classes, unless the class is `object`, and classes c
In addition to the members declared in the record body, a record type has additional synthesized members.
Members are synthesized unless a member with a "matching" signature is declared in the record body or
an accessible concrete non-virtual member with a "matching" signature is inherited.
an accessible concrete non-virtual member with a "matching" signature is inherited. A matching member prevents the compiler from generating that member, not any other synthesized members.
Two members are considered matching if they have the same
signature or would be considered "hiding" in an inheritance scenario.
It is an error for a member of a record to be named "Clone".
@ -232,8 +232,9 @@ bool PrintMembers(System.Text.StringBuilder builder);
The method is `private` if the record type is `sealed`. Otherwise, the method is `virtual` and `protected`.
The method:
1. for each of the record's printable members (non-static public field and readable property members), appends that member's name followed by " = " followed by the member's value separated with ", ",
2. return true if the record has printable members.
1. calls the method `System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()` if the method is present and the record has printable members.
2. for each of the record's printable members (non-static public field and readable property members), appends that member's name followed by " = " followed by the member's value separated with ", ",
3. return true if the record has printable members.
For a member that has a value type, we will convert its value to a string representation using the most efficient method available to the target platform. At present that means calling `ToString` before passing to `StringBuilder.Append`.
@ -385,6 +386,7 @@ For a record:
* A public `get` and `init` auto-property is created (see separate `init` accessor specification).
An inherited `abstract` property with matching type is overridden.
It is an error if the inherited property does not have `public` overridable `get` and `init` accessors.
It is an error if the inherited property is hidden.
The auto-property is initialized to the value of the corresponding primary constructor parameter.
Attributes can be applied to the synthesized auto-property and its backing field by using `property:` or `field:`
targets for attributes syntactically applied to the corresponding record parameter.

View file

@ -1,10 +1,5 @@
# Suppress emitting of `localsinit` flag.
* [x] Proposed
* [ ] Prototype: Not Started
* [ ] Implementation: Not Started
* [ ] Specification: Not Started
## Summary
[summary]: #summary
@ -88,4 +83,4 @@ Does not address the most requested scenario and may turn code unverifiable with
## Design meetings
None yet.
None yet.

View file

@ -1,11 +1,5 @@
# Target-typed `new` expressions
* [x] Proposed
* [x] Prototype
* [ ] Implementation
* [ ] Specification
## Summary
[summary]: #summary

View file

@ -1,10 +1,5 @@
# Top-level statements
* [x] Proposed
* [x] Prototype: Started
* [x] Implementation: Started
* [ ] Specification: Not Started
## Summary
[summary]: #summary

View file

@ -1,479 +0,0 @@
# Improved Interpolated Strings
## Summary
We introduce a new pattern for creating and using interpolated string expressions to allow for efficient formatting and use in both general `string` scenarios
and more specialized scenarios such as logging frameworks, without incurring unnecessary allocations from formatting the string in the framework.
## Motivation
Today, string interpolation mainly lowers down to a call to `string.Format`. This, while general purpose, can be inefficient for a number of reasons:
1. It boxes any struct arguments, unless the runtime has happened to introduce an overload of `string.Format` that takes exactly the correct types of arguments
in exactly the correct order.
* This ordering is why the runtime is hesitant to introduce generic versions of the method, as it would lead to combinatoric explosion of generic instantiations
of a very common method.
2. It has to allocate an array for the arguments in most cases.
3. There is no opportunity to avoid instanciating the instance if it's not needed. Logging frameworks, for example, will recommend avoiding string interpolation
because it will cause a string to be realized that may not be needed, depending on the current log-level of the application.
4. It can never use `Span` or other ref struct types today, because ref structs are not allowed as generic type parameters, meaning that if a user wants to avoid
copying to intermediate locations they have to manually format strings.
Internally, the runtime has a type called `ValueStringBuilder` to help deal with the first 2 of these scenarios. They pass a stackalloc'd buffer to the builder,
repeatedly call `AppendFormat` with every part, and then get a final string out. If the resulting string goes past the bounds of the stack buffer, they can then
move to an array on the heap. However, this type is dangerous to expose directly, as incorrect usage could lead to a rented array to be double-disposed, which
then will cause all sorts of undefined behavior in the program as two locations think they have sole access to the rented array. This proposal creates a way to
use this type safely from native C# code by just writing an interpolated string literal, leaving written code unchanged while improving every interpolated string
that a user writes. It also extends this pattern to allow for interpolated strings passed as arguments to other methods to use a builder pattern, defined by
receiver of the method, that will allow things like logging frameworks to avoid allocating strings that will never be needed, and giving C# users familiar,
convenient interpolation syntax.
## Detailed Design
### The builder pattern
We introduce a new builder pattern that can represent an interpolated string passed as an argument to a method. The simple English of the pattern is as follows:
When an _interpolated\_string\_expression_ is passed as an argument to a method, we look at the type of the parameter. If the parameter type has a static method
`Create` that can be invoked with 2 int parameters, `literalLength` and `formattedCount`, optionally takes a parameter the receiver is convertible to,
and has an out parameter of the type of original method's parameter and that type has instance `AppendLiteral` and `AppendFormatted` methods that
can be invoked for every part of the interpolated string, then we lower the interpolation using that, instead of into a traditional call to
`string.Format(formatStr, args)`. A more concrete example is helpful for picturing this:
```cs
// The builder that will actually "build" the interpolated string"
public ref struct TraceLoggerParamsBuilder
{
public static TraceLoggerParamsBuilder Create(int literalLength, int formattedCount, Logger logger, out bool builderIsValid)
{
if (!logger._logLevelEnabled)
{
builderIsValid = false;
return default;
}
builderIsValid = true;
return TraceLoggerParamsBuilder(literalLength, formattedCount, logger.EnabledLevel);
}
// Storage for the built-up string
private bool _logLevelEnabled;
private TraceLoggerParamsBuilder(int literalLength, int formattedCount, bool logLevelEnabled)
{
// Initialization logic
_logLevelEnabled = logLevelEnabled
}
public bool AppendLiteral(string s)
{
// Store and format part as required
return true;
}
public bool AppendFormatted<T>(T t)
{
// Store and format part as required
return true;
}
}
// The logger class. The user has an instance of this, accesses it via static state, or some other access
// mechanism
public class Logger
{
// Initialization code omitted
public LogLevel EnabledLevel;
public void LogTrace(TraceLoggerParamsBuilder builder)
{
// Impl of logging
}
}
Logger logger = GetLogger(LogLevel.Info);
// Given the above definitions, usage looks like this:
var name = "Fred Silberberg";
logger.LogTrace($"{name} will never be printed because info is < trace!");
// This is converted to:
var name = "Fred Silberberg";
var receiverTemp = logger;
var builder = TraceLoggerParamsBuilder.Create(literalLength: 47, formattedCount: 1, receiverTemp, out var builderIsValid);
_ = builderIsValid &&
builder.AppendFormatted(name) &&
builder.AppendLiteral(" will never be printed because info is < trace!");
receiverTemp.LogTrace(builder);
```
Here, because `TraceLoggerParamsBuilder` has static method called `Create` with the correct parameters and returns the type the `LogTrace` call was expecting,
we say that the interpolated string has an implicit builder conversion to that parameter, and it lowers to the pattern shown above. The specese needed for this
is a bit complicated, and is expanded below.
#### Builder type applicability
A type is said to be an _applicable\_interpolated\_string\_builder\_type_ if, given an _interpolated\_string\_literal_ `S`, the following is true:
* Overload resolution with an identifier of `AppendLiteral` and a parameter type of `string` succeeds, and contains a single instance method that returns a `bool` or `void`.
* For every _regular\_balanced\_text_ component of `S` (`Si`) without an _interpolation\_format_ component or _constant\_expression_ (alignment) component, overload resolution
with an identifier of `AppendFormatted` and parameter of the type of `Si` succeeds, and contains a single instance method that returns a `bool` or `void`.
* For every _regular\_balanced\_text_ component of `S` (`Si`) with an _interpolation\_format_ component and no _constant\_expression_ (alignment) component, overload resolution
with an identifier of `AppendFormatted` and parameter types of `Si` and `string` with name `format` (in that order) succeeds, and contains a single instance method that returns
a `bool` or `void`.
* For every _regular\_balanced\_text_ component of `S` (`Si`) with a _constant\_expression_ (alignment) component and no _interpolation\_format_ component, overload resolution
with an identifier of `AppendFormatted` and parameter types of `Si` and `int` with name `alignment` (in that order) succeeds, and contains a single instance method that returns
a `bool` or `void`.
* For every _regular\_balanced\_text_ component of `S` (`Si`) with an _interpolation\_format_ component and a _constant\_expression_ (alignment) component, overload resolution
with an identifier of `AppendFormatted` and parameter types of `Si`, `int` with name `format`, and `string` with name `alignment` (in that order) succeeds, and contains a
single instance method that returns a `bool` or `void`.
Addionally, all calls to `AppendLiteral` or `AppendFormat` must return the same type. It is not permitted to mix `bool` and `void` returning methods.
The rest of this proposal will use `Append...` to refer to either of `AppendLiteral` or `AppendFormatted` in cases when both are applicable.
Note that these rules do not permit extension methods for the `Append...` calls. We could consider enabling that if we choose, but this is analogous to the enumerator
pattern, where we allow `GetEnumerator` to be an extension method, but not `Current` or `MoveNext()`.
These rules _do_ permit default parameters for the `Append...` calls, which will work with things like `CallerLineNumber` or `CallerArgumentExpression` (when supported by
the language).
We have separate overload lookup rules for base elements vs interpolation holes because some builders will want to be able to understand the difference between the components
that were interpolated and the components that were part of the base string.
**Open Question**
Some scenarios, like structured logging, want to be able to provide names for interpolation elements. For example, today a logging call might look like
`Log("{name} bought {itemCount} items", name, items.Count);`. The names inside the `{}` provide important structure information for loggers that help with ensuring output
is consistent and uniform. Some cases might be able to reuse the `:format` component of an interpolation hole for this, but many loggers already understand format specifiers
and have existing behavior for output formatting based on this info. Is there some syntax we can use to enable putting these named specifiers in?
Some cases may be able to get away with `CallerArgumentExpression`, provided that support does land in C# 10. But for cases that invoke a method/property, that may not be
sufficient.
#### Interpolated string builder conversion
We add a new implicit conversion type: The _implicit\_string\_builder\_conversion_. An _implicit\_string\_builder\_conversion_ permits an _interpolated\_string\_expression_
to be converted to an _applicable\_interpolated\_string\_builder\_type_. There are 2 ways that this conversion can occur:
1. A method argument is converted as part of determining applicable function members (covered below), or
2. Given an _interpolated\_string\_expression_ `S` being converted to type `T`, the following is true:
* `T` is an _applicable\_interpolated\_string\_builder\_type_, and
* One of the following is true:
* `T` has an accessible static T-returning method `Create` that takes 2 int parameters and 1 out parameter of type `bool`, in that order.
* `T` has an accessible static T-returning method `Create` that takes 2 int parameters, in that order.
By requiring `Create` to be a `static` method instead of a constructor, we allow the implementation to pool builders if it so decides to.
If we limited the pattern to constructors, then the implementation would be required to always return new instances.
Additionally, by returning the builder type instead of requiring it to go in an out parameter, we marginally improve the codegen and significantly simplify the rules around
ref struct lifetimes vs the traditional .NET `TryX` pattern, and we expect a number of these builder types to be ref structs.
This can and will be impacted by abstract statics in interfaces for generic contexts. We will need to make sure the interactions are considered and tested.
**~~Open~~ Question**
Need to confirm this pattern.
_Answer_: Confirmed.
#### Applicable function member adjustments
We adjust the wording of the [applicable function member algorithm](https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#applicable-function-member)
as follows (a new sub-bullet is added at the front of each section, in bold):
A function member is said to be an ***applicable function member*** with respect to an argument list `A` when all of the following are true:
* Each argument in `A` corresponds to a parameter in the function member declaration as described in [Corresponding parameters](expressions.md#corresponding-parameters), and any parameter to which no argument corresponds is an optional parameter.
* For each argument in `A`, the parameter passing mode of the argument (i.e., value, `ref`, or `out`) is identical to the parameter passing mode of the corresponding parameter, and
* **for an interpolated string argument to a value parameter when `A` is an instance method or static extension method invoked in reduced form, the type of the corresponding parameter is an _applicable\_interpolated\_string\_builder\_type_ `Ai`, and one of the following overload resolutions succeeds. An interpolated string argument applicable in this way is said to be immediately converted to the corresponding parameter type with an _implicit\_string\_builder\_conversion_. Or,**
* **Overload resolution on `Ai` with the identifier `Create` and a parameter list of 2 int parameters, the receiver type of `A`, and an out parameter of type `bool` succeeds with 1 invocable member, and the return type of that member is `Ai`, or**
* **Overload resolution on `Ai` with the identifier `Create` and a parameter list of 2 int parameters and the receiver type of `A` succeeds with 1 invocable member, and the return type of that member is `Ai`.**
* for a value parameter or a parameter array, an implicit conversion ([Implicit conversions](conversions.md#implicit-conversions)) exists from the argument to the type of the corresponding parameter, or
* for a `ref` or `out` parameter, the type of the argument is identical to the type of the corresponding parameter. After all, a `ref` or `out` parameter is an alias for the argument passed.
For a function member that includes a parameter array, if the function member is applicable by the above rules, it is said to be applicable in its ***normal form***. If a function member that includes a parameter array is not applicable in its normal form, the function member may instead be applicable in its ***expanded form***:
* The expanded form is constructed by replacing the parameter array in the function member declaration with zero or more value parameters of the element type of the parameter array such that the number of arguments in the argument list `A` matches the total number of parameters. If `A` has fewer arguments than the number of fixed parameters in the function member declaration, the expanded form of the function member cannot be constructed and is thus not applicable.
* Otherwise, the expanded form is applicable if for each argument in `A` the parameter passing mode of the argument is identical to the parameter passing mode of the corresponding parameter, and
* **for an interpolated string argument to a fixed value parameter or a value parameter created by the expansion when `A` is an instance method or static extension method invoked in reduced form, the type of the corresponding parameter is an _applicable\_interpolated\_string\_builder\_type_ `Ai`, and one of the following overload resolutions succeeds. An interpolated string argument applicable in this way is said to be immediately converted to the corresponding parameter type with an _implicit\_string\_builder\_conversion_. Or,**
* **Overload resolution on `Ai` with the identifier `Create` and a parameter list of 2 int parameters, the receiver type of `A`, and an out parameter of type `bool` succeeds with 1 invocable member, and the return type of that member is `Ai`, or**
* **Overload resolution on `Ai` with the identifier `Create` and a parameter list of 2 int parameters and the receiver type of `A` succeeds with 1 invocable member, and the return type of that member is `Ai`.**
* for a fixed value parameter or a value parameter created by the expansion, an implicit conversion ([Implicit conversions](conversions.md#implicit-conversions)) exists from the type of the argument to the type of the corresponding parameter, or
* for a `ref` or `out` parameter, the type of the argument is identical to the type of the corresponding parameter.
Important note: this means that if there are 2 otherwise equivalent overloads, that only differ by the type of the _applicable\_interpolated\_string\_builder\_type_, these overloads will
be considered ambiguous. We could potentially make changes to the better function member algorithm to resolve this if we so choose, but this scenario unlikely to occur and isn't a priority
to address.
Another important note is that, for a single overload, priority will be given to the builder construction method that takes a receiver type over builder construction that does not. This is
because the receiver version is checked for applicability before we look for general conversions, and this ordering is desirable.
#### Better conversion from expression adjustments
We change the [better conversion from expression](https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#better-conversion-from-expression) section to the
following:
Given an implicit conversion `C1` that converts from an expression `E` to a type `T1`, and an implicit conversion `C2` that converts from an expression `E` to a type `T2`, `C1` is a ***better conversion*** than `C2` if:
1. `E` is a non-constant _interpolated\_string\_expression_, `C1` is an _implicit\_string\_builder\_conversion_, `T1` is an _applicable\_interpolated\_string\_builder\_type_, and `C2` is not an _implicit\_string\_builder\_conversion_, or
2. `E` does not exactly match `T2` and at least one of the following holds:
* `E` exactly matches `T1` ([Exactly matching Expression](expressions.md#exactly-matching-expression))
* `T1` is a better conversion target than `T2` ([Better conversion target](expressions.md#better-conversion-target))
This does mean that there are some potentially non-obvious overload resolution rules, depending on whether the interpolated string in question is a constant-expression or not. For example:
```cs
void Log(string s) { ... }
void Log(TraceLoggerParamsBuilder p) { ... }
Log($""); // Calls Log(string s), because $"" is a constant expression
Log($"{"test"}"); // Calls Log(string s), because $"{"test"}" is a constant expression
Log($"{1}"); // Calls Log(TraceLoggerParamsBuilder p), because $"{1}" is not a constant expression
```
This is introduced so that things that can simply be emitted as constants do so, and don't incur any overhead, while things that cannot be constant use the builder pattern.
### InterpolatedStringBuilder and Usage
We introduce a new type in `System.Runtime.CompilerServices`: `InterpolatedStringBuilder`. This is a ref struct with many of the same semantics as `ValueStringBuilder`,
intended for direct use by the C# compiler. This struct would look approximately like this:
```cs
// API Proposal issue: https://github.com/dotnet/runtime/issues/50601
namespace System.Runtime.CompilerServices
{
public ref struct InterpolatedStringBuilder
{
public static InterpolatedStringBuilder Create(int literalLength, int formattedCount);
public string ToStringAndClear();
public void AppendLiteral(string value);
public void AppendFormatted<T>(T value);
public void AppendFormatted<T>(T value, string? format);
public void AppendFormatted<T>(T value, int alignment);
public void AppendFormatted<T>(T value, int alignment, string? format);
public void AppendFormatted(ReadOnlySpan<char> value);
public void AppendFormatted(ReadOnlySpan<char> value, int alignment = 0, string? format = null);
public void AppendFormatted(string? value);
public void AppendFormatted(string? value, int alignment = 0, string? format = null);
public void AppendFormatted(object? value, int alignment = 0, string? format = null);
}
}
```
We make a slight change to the rules for the meaning of an [_interpolated\_string\_expression_](https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#interpolated-strings):
**If the type of an interpolated string is `string` and the type `System.Runtime.CompilerServices.InterpolatedStringBuilder` exists, and the current context supports using that type, the string**
**is lowered using the builder pattern. The final `string` value is then obtained by calling `ToStringAndClear()` on the builder type.**
**Otherwise, if** the type of an interpolated string is `System.IFormattable` or `System.FormattableString` [the rest is unchanged]
The "and the current context supports using that type" rule is intentionally vague to give the compiler leeway in optimizing usage of this pattern. The builder type is likely to be a ref struct
type, and ref struct types are normally not permitted in async methods. For this particular case, the compiler would be allowed to make use the builder if none of the interpolation holes contain
an `await` expression, as we can statically determine that the builder type is safely used without additional complicated analysis because the builder will be dropped after the interpolated string
expression is evaluated.
**~~Open~~ Question**:
Do we want to instead just make the compiler know about `InterpolatedStringBuilder` and skip the `string.Format` call entirely? It would allow us to hide a method that we don't necessarily
want to put in people's faces when they manually call `string.Format`.
_Answer_: Yes.
**~~Open~~ Question**:
Do we want to have builders for `System.IFormattable` and `System.FormattableString` as well?
_Answer_: No.
### Lowering
Both the general pattern and the specific changes for interpolated strings directly converted to `string`s follow the same lowering pattern. The `Create` method is
invoked on the receiver (whether that's the temporary method receiver for an _implicit\_string\_builder\_conversion_ derived from the applicable function member algorithm, or a
standard conversion derived from the target type). If the call returned `true`, `Append...` is repeatedly invoked on the builder out parameter, with each part of the interpolated string,
in order, stopping subsequent calls if a `Append...` call returns `false`. Finally, the original method is called, passing the initialized builder in place of the interpolated string expression.
**~~Open~~ Question**
This lowering means that subsequent parts of the interpolated string after a false-returning `Append...` call don't get evaluated. This could potentially be very confusing, particularly
if the format hole is side-effecting. We could instead evaluate all format holes first, then repeatedly call `Append...` with the results, stopping if it returns false. This would ensure
that all expressions get evaluated as one might expect, but we call as few methods as we need to. While the partial evaluation might be desirable for some more advanced cases, it is perhaps
non-intuitive for the general case.
Another alternative, if we want to always evaluate all format holes, is to remove the `Append...` version of the API and just do repeated `Format` calls. The builder can track whether it
should just be dropping the argument and immediately returning for this version.
_Answer_: We will have conditional evaluation of the holes.
**~~Open~~ Question**
Do we need to dispose of disposable builder types, and wrap calls with try/finally to ensure that Dispose is called? For example, the interpolated string builder in the bcl might have a
rented array inside it, and if one of the interpolation holes throws an exception during evaluation, that rented array could be leaked if it wasn't disposed.
_Answer_: No. Builders can be assigned to locals (such as `MyBuilder builder = $"{MyCode()};`), and the lifetime of such builders is unclear. Unlike foreach enumerators, where the lifetime
is obvious and no user-defined local is created for the enumerator.
## Other considerations
### Allow `string` types to be convertible to builders as well
For type author simplicity, we could consider allowing expressions of type `string` to be implicitly-convertible to _applicable\_interpolated\_string\_builder\_types_. As proposed today,
authors will likely need to overload on both that builder type and regular `string` types, so their users don't have to understand the difference. This may be an annoying and non-obvious
overhead, as a `string` expression can be viewed as an interpolation with `expression.Length` prefilled length and 0 holes to be filled.
This would allow new APIs to only expose a builder, without also having to expose a `string`-accepting overload. However, it won't get around the need for changes to better conversion from
expression, so while it would work it may be unnecessary overhead.
### Incorporating spans for heap-less strings
`ValueStringBuilder` as it exists today has 2 constructors: one that takes a count, and allocates on the heap eagerly, and one that takes a `Span<char>`. That `Span<char>` is usually
a fixed size in the runtime codebase, around 250 elements on average. To truly replace that type, we should consider an extension to this where we also recognize `GetInterpolatedString`
methods that take a `Span<char>`, instead of just the count version. However, we see a few potential thorny cases to resolve here:
* We don't want to stackalloc repeatedly in a hot loop. If we were to do this extension to the feature, we'd likely want to share the stackalloc'd span between loop
iterations. We know this is safe, as `Span<T>` is a ref struct that can't be stored on the heap, and users would have to be pretty devious to manage to extract a
reference to that `Span` (such as creating a method that accepts such a builder then deliberately retrieving the `Span` from the builder and returning it to the
caller). However, allocating ahead of time produces other questions:
* Should we eagerly stackalloc? What if the loop is never entered, or exits before it needs the space?
* If we don't eagerly stackalloc, does that mean we introduce a hidden branch on every loop? Most loops likely won't care about this, but it could affect some tight loops that don't
want to pay the cost.
* Some strings can be quite big, and the appropriate amount to `stackalloc` is dependent on a number of factors, including runtime factors. We don't really want the C# compiler and
specification to have to determine this ahead of time, so we'd want to resolve https://github.com/dotnet/runtime/issues/25423 and add an API for the compiler to call in these cases. It
also adds more pros and cons to the points from the previous loop, where we don't want to potentially allocate large arrays on the heap many times or before one is needed.
### Non-try version of the API
For simplicity, this spec currently just proposes recognizing a `Append...` method, and things that always succeed (like `InterpolatedStringBuilder`) would always return true from the method.
This was done to support partial formatting scenarios where the user wants to stop formatting if an error occurs or if it's unnecessary, such as the logging case, but could potentially
introduce a bunch of unnecessary branches in standard interpolated string usage. We could consider an addendum where we use just `FormatX` methods if no `Append...` method is present, but
it does present questions about what we do if there's a mix of both `Append...` and `FormatX` calls.
_Answer_:
We want the non-try version of the API. The proposal has been updated to reflect this.
### Passing previous arguments to the builder
There is unfortunate lack of symmetry in the proposal at it currently exists: invoking an extension method in reduced form produces different semantics than invoking the extension method in
normal form. This is different from most other locations in the language, where reduced form is just a sugar. We propose adding an attribute to the framework that we will recognize when
binding a method, that informs the compiler that certain parameters should be passed to the `Create` method on the builder. Usage looks like this:
```cs
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class InterpolatedBuilderArgumentAttribute : Attribute
{
public InterpolatedBuilderArgumentAttribute(string argument);
public InterpolatedBuilderArgumentAttribute(params string[] arguments);
public string[] Arguments { get; }
}
}
```
Usage of this is then:
```cs
namespace System
{
public sealed class String
{
public static string Format(IFormatProvider? provider, [InterpolatedBuilderArgument("provider")] ref InterpolatedStringBuilder builder);
}
}
namespace System.Runtime.CompilerServices
{
public ref struct InterpolatedStringBuilder
{
public static InterpolatedStringBuilder Create(int baseLength, int holeCount, IFormatProvider? provider); // additional factory
}
}
var formatted = string.Format(CultureInfo.InvariantCulture, $"{X} = {Y}");
// Is lowered to
var tmp1 = CultureInfo.InvariantCulture;
var builder = InterpolatedStringBuilder.Create(3, 2, tmp1);
builder.AppendFormatted(X);
builder.AppendLiteral(" = ");
builder.AppendFormatted(Y);
var formatted = string.Format(tmp1, builder);
```
The questions we need to answer:
1. Do we like this pattern in general?
2. Do we want to allow these arguments to come from after the builder parameter? Some existing patterns in the BCL, such as `Utf8Formatter`, put the value to be formatted _before_ the thing
needed to format into. To fit in best with these patterns, we'd likely want to allow this, but we need to decide if this out-of-order evaluate is ok.
### `await` usage in interpolation holes
Because `$"{await A()}"` is a valid expression today, we need to rationalize how interpolation holes with await. We could solve this with a few rules:
1. If an interpolated string used as a `string`, `IFormattable`, or `FormattableString` has an `await` in an interpolation hole, fall back to old-style formatter.
2. If an interpolated string is subject to an _implicit\_string\_builder\_conversion_ and _applicable\_interpolated\_string\_builder\_type_ is a `ref struct`, `await` is not allowed to be used
in the format holes.
Fundamentally, this desugaring could use a ref struct in an async method as long as we guarantee that the `ref struct` will not need to be saved to the heap, which should be possible if we forbid
`await`s in the interpolation holes.
Alternatively, we could simply make all builder types non-ref structs, including the framework builder for interpolated strings. This would, however, preclude us from someday recognizing a `Span`
version that does not need to allocate any scratch space at all.
### Builders as ref parameters
Some builders might want to be passed as ref parameters (either `in` or `ref`). Should we allow either? And if so, what will a `ref` builder look like? `ref $""` is confusing, as you're not actually
passing the string by ref, you're passing the builder that is created from the ref by ref, and has similar potential issues with async methods.
### Interpolated strings through binary expressions and conversions
Because this proposal makes interpolated strings context sensitive, we would like to allow the compiler to treat a binary expression composed entirely of interpolated strings,
or an interpolated string subjected to a cast, as an interpolated string literal for the purposes of overload resolution. For example, take the following scenario:
```cs
struct Builder1
{
public static Builder1 Create(int literalLength, int formattedCount, C c) => ...;
// AppendX... methods as necessary
}
struct Builder2
{
public static Builder2 Create(int literalLength, int formattedCount, C c) => ...;
// AppendX... methods as necessary
}
class C
{
void M(Builder1 builder) => ...;
void M(Builder2 builder) => ...;
}
c.M($"{X}"); // Ambiguous between the M overloads
```
This would be ambiguous, necessitating a cast to either `Builder1` or `Builder2` in order to resolve. However, in making that cast, we would potentially throw away the information
that there is context from the method receiver, meaning that the cast would fail because there is nothing to fill in the information of `c`. A similar issue arises with binary concatenation
of strings: the user could want to format the literal across several lines to avoid line wrapping, but would not be able to because that would no longer be an interpolated string literal
convertible to the builder type.
To resolve these cases, we make the following changes:
* An _additive\_expression_ composed entirely of _interpolated\_string\_expressions_ and using only `+` operators is considered to be an _interpolated\_string\_literal_ for the purposes of
conversions and overload resolution. The final interpolated string is created by logically concatinating all individual _interpolated\_string\_expression_ components, from left to right.
* A _cast\_expression_ or a _relational\_expression_ with operator `as` whose operand is an _interpolated\_string\_expressions_ is considered an _interpolated\_string\_expressions_ for the
purposes of conversions and overload resolution.
**Open Questions**:
Do we want to do this? We don't do this for `System.FormattableString`, for example, but that can be broken out onto a different line, whereas this can be context-dependent and therefore not
able to be broken out into a different line. There are also no overload resolution concerns with `FormattableString` and `IFormattable`.
## Other use cases
See https://github.com/dotnet/runtime/issues/50635 for examples of proposed builder APIs using this pattern.

View file

@ -1,111 +0,0 @@
# Lambda Attributes
* [x] Proposed
* [ ] Prototype
* [ ] Implementation
* [ ] Specification
## Summary
[summary]: #summary
Allow attributes to be applied to lambdas (and anonymous methods) and to lambda / anonymous method parameters, as they can be on regular methods.
## Motivation
[motivation]: #motivation
Two primary motivations:
1. To provide metadata visible to analyzers at compile-time.
2. To provide metadata visible to reflection and tooling at run-time.
As an example of (1):
For performance-sensitive code, it is helpful to be able to have an analyzer that flags when closures and delegates are being allocated for lambdas that close over state. Often a developer of such code will go out of his or her way to avoid capturing any state, so that the compiler can generate a static method and a cacheable delegate for the method, or the developer will ensure that the only state being closed over is `this`, allowing the compiler at least to avoid allocating a closure object. But, without language support for limiting what may be captured, it is all too easy to accidentally close over state. It would be valuable if a developer could annotate lambdas with attributes to indicate what state they're allowed to close over, for example:
```csharp
[CaptureNone] // can't close over any instance state
[CaptureThis] // can only capture `this` and no other instance state
[CaptureAny] // can close over any instance state
```
Then an analyzer can be written to flag when state is captured incorrectly, for example:
```csharp
var results = collection.Select([CaptureNone](i) => Process(item)); // Analyzer error: [CaptureNone] lambdas captures `this`
...
private U Process(T item) { ... }
```
## Detailed design
[design]: #detailed-design
- Using the same attribute syntax as on normal methods, attributes may be applied at the beginning of a lambda or anonymous method, for example:
```csharp
[SomeAttribute(...)] () => { ... }
[SomeAttribute(...)] delegate (int i) { ... }
```
- To avoid ambiguity as to whether an attribute applies to the lambda method or to one of the arguments, attributes may only be used when parens are used around any arguments, for example:
```csharp
[SomeAttribute] i => { ... } // ERROR
[SomeAttribute] (i) => { ... } // Ok
[SomeAttribute] (int i) => { ... } // Ok
```
- With anonymous methods, parens are not needed in order to apply an attribute to the method before the `delegate` keyword, for example:
```csharp
[SomeAttribute] delegate { ... } // Ok
[SomeAttribute] delegate (int i) => { ... } // Ok
```
- Multiple attributes may be applied, either via standard comma-delimited syntax or via full-attribute syntax, for example:
```csharp
[FirstAttribute, SecondAttribute] (i) => { ... } // Ok
[FirstAttribute] [SecondAttribute] (i) => { .... } // Ok
```
- Attributes may be applied to the parameters to an anonymous method or lambda, but only when parens are used around any arguments, for example:
```csharp
[SomeAttribute] i => { ... } // ERROR
([SomeAttribute] i) => { .... } // Ok
([SomeAttribute] int i) => { ... } // Ok
([SomeAttribute] i, [SomeOtherAttribute] j) => { ... } // Ok
```
- Multiple attributes may be applied to the parameters of an anonymous method or lambda, using either the comma-delimited or full-attribute syntax, for example:
```csharp
([FirstAttribute, SecondAttribute] i) => { ... } // Ok
([FirstAttribute] [SecondAttribute] i) => { ... } // Ok
```
- `return`-targeted attributes may also be used on lambdas, for example:
```csharp
([return: SomeAttribute] (i) => { ... }) // Ok
```
- The compiler outputs the attributes onto the generated method and arguments to those methods as it would for any other method.
## Drawbacks
[drawbacks]: #drawbacks
n/a
## Alternatives
[alternatives]: #alternatives
n/a
## Unresolved questions
[unresolved]: #unresolved-questions
n/a
## Design meetings
n/a

View file

@ -1,204 +0,0 @@
# Lambda improvements
## Summary
Proposed changes:
1. Allow lambdas with attributes
2. Allow lambdas with explicit return type
3. Infer a natural delegate type for lambdas and method groups
## Motivation
Support for attributes on lambdas would provide parity with methods and local functions.
Support for explicit return types would provide symmetry with lambda parameters where explicit types can be specified.
Allowing explicit return types would also provide control over compiler performance in nested lambdas where overload resolution must bind the lambda body currently to determine the signature.
A natural type for lambda expressions and method groups will allow more scenarios where lambdas and method groups may be used without an explicit delegate type, including as initializers in `var` declarations.
Requiring explicit delegate types for lambdas and method groups has been a friction point for customers, and has become an impediment to progress in ASP.NET with recent work on [MapAction](https://github.com/dotnet/aspnetcore/pull/29878).
[ASP.NET MapAction](https://github.com/dotnet/aspnetcore/pull/29878) without proposed changes (`MapAction()` takes a `System.Delegate` argument):
```csharp
[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name");
app.MapAction((Func<Todo>)GetTodo);
[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo);
app.MapAction((Func<Todo, Todo>)PostTodo);
```
[ASP.NET MapAction](https://github.com/dotnet/aspnetcore/pull/29878) with natural types for method groups:
```csharp
[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name");
app.MapAction(GetTodo);
[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo);
app.MapAction(PostTodo);
```
[ASP.NET MapAction](https://github.com/dotnet/aspnetcore/pull/29878) with attributes and natural types for lambda expressions:
```csharp
app.MapAction([HttpGet("/")] () => new Todo(Id: 0, Name: "Name"));
app.MapAction([HttpPost("/")] ([FromBody] Todo todo) => todo);
```
## Attributes
Attributes may be added to lambda expressions.
```csharp
f = [MyAttribute] x => x; // [MyAttribute]lambda
f = [MyAttribute] (int x) => x; // [MyAttribute]lambda
f = [MyAttribute] static x => x; // [MyAttribute]lambda
f = [return: MyAttribute] () => 1; // [return: MyAttribute]lambda
```
_Should parentheses be required for the parameter list if attributes are added to the entire expression? (Should `[MyAttribute] x => x` be disallowed? If so, what about `[MyAttribute] static x => x`?)_
Attributes may be added to lambda parameters that are declared with explicit types.
```csharp
f = ([MyAttribute] x) => x; // syntax error
f = ([MyAttribute] int x) => x; // [MyAttribute]x
```
Attributes are not supported for anonymous methods declared with `delegate { }` syntax.
```csharp
f = [MyAttribute] delegate { return 1; }; // syntax error
f = delegate ([MyAttribute] int x) { return x; }; // syntax error
```
Attributes on the lambda expression or lambda parameters will be emitted to metadata on the method that maps to the lambda.
In general, customers should not depend on how lambda expressions and local functions map from source to metadata. How lambdas and local functions are emitted can, and has, changed between compiler versions.
The changes proposed here are targeted at the `Delegate` driven scenario.
It should be valid to inspect the `MethodInfo` associated with a `Delegate` instance to determine the signature of the lambda expression or local function including any explicit attributes and additional metadata emitted by the compiler such as default parameters.
This allows teams such as ASP.NET to make available the same behaviors for lambdas and local functions as ordinary methods.
## Explicit return type
An explicit return type may be specified after the parameter list.
```csharp
f = () : T => default; // () : T
f = x : short => 1; // <unknown> : short
f = (ref int x) : ref int => ref x; // ref int : ref int
f = static _ : void => { }; // <unknown> : void
```
Explicit return types are not supported for anonymous methods declared with `delegate { }` syntax.
```csharp
f = delegate : int { return 1; }; // syntax error
f = delegate (int x) : int { return x; }; // syntax error
```
## Natural delegate type
A lambda expression has a natural type if the parameters types are explicit and either the return type is explicit or there is a common type from the natural types of all `return` expressions in the body.
The natural type is a delegate type where the parameter types are the explicit lambda parameter types and the return type `R` is:
- if the lambda return type is explicit, that type is used;
- if the lambda has no return expressions, the return type is `void` or `System.Threading.Tasks.Task` if `async`;
- if the common type from the natural type of all `return` expressions in the body is the type `R0`, the return type is `R0` or `System.Threading.Tasks.Task<R0>` if `async`.
A method group has a natural type if the method group contains a single method.
A method group might refer to extension methods. Normally method group resolution searches for extension methods lazily, only iterating through successive namespace scopes until extension methods are found that match the target type. But to determine the natural type will require searching all namespace scopes. _To minimize unnecessary binding, perhaps natural type should be calculated only in cases where there is no target type - that is, only calculate the natural type in cases where it is needed._
The delegate type for the lambda or method group and parameter types `P1, ..., Pn` and return type `R` is:
- if any parameter or return value is not by value, or there are more than 16 parameters, or any of the parameter types or return are not valid type arguments (say, `(int* p) => { }`), then the delegate is a synthesized `internal` anonymous delegate type with signature that matches the lambda or method group, and with parameter names `arg1, ..., argn` or `arg` if a single parameter;
- if `R` is `void`, then the delegate type is `System.Action<P1, ..., Pn>`;
- otherwise the delegate type is `System.Func<P1, ..., Pn, R>`.
`modopt()` or `modreq()` in the method group signature are ignored in the corresponding delegate type.
If two lambda expressions or method groups in the same compilation require synthesized delegate types with the same parameter types and modifiers and the same return type and modifiers, the compiler will use the same synthesized delegate type.
Lambdas or method groups with natural types can be used as initializers in `var` declarations.
```csharp
var f1 = () => default; // error: no natural type
var f2 = x => { }; // error: no natural type
var f3 = x => x; // error: no natural type
var f4 = () => 1; // System.Func<int>
var f5 = () : string => null; // System.Func<string>
```
```csharp
static void F1() { }
static void F1<T>(this T t) { }
static void F2(this string s) { }
var f6 = F1; // error: multiple methods
var f7 = "".F1; // System.Action
var f8 = F2; // System.Action<string>
```
The synthesized delegate types are implicitly co- and contra-variant.
```csharp
var fA = (IEnumerable<string> e, ref int i) => { }; // void DA$(IEnumerable<string>, ref int);
fA = (IEnumerable<object> e, ref int i) => { }; // ok
var fB = (IEnumerable<object> e, ref int i) => { }; // void DB$(IEnumerable<object>, ref int);
fB = (IEnumerable<string> e, ref int i) => { }; // error: parameter type mismatch
```
### Implicit conversion to `System.Delegate`
A consequence of inferring a natural type is that lambda expressions and method groups with natural type are implicitly convertible to `System.Delegate`.
```csharp
static void Invoke(Func<string> f) { }
static void Invoke(Delegate d) { }
static string GetString() => "";
static int GetInt() => 0;
Invoke(() => ""); // Invoke(Func<string>)
Invoke(() => 0); // Invoke(Delegate) [new]
Invoke(GetString); // Invoke(Func<string>)
Invoke(GetInt); // Invoke(Delegate) [new]
```
If a natural type cannot be inferred, there is no implicit conversion to `System.Delegate`.
```csharp
static void Invoke(Delegate d) { }
Invoke(Console.WriteLine); // error: cannot to 'Delegate'; multiple candidate methods
Invoke(x => x); // error: cannot to 'Delegate'; no natural type for 'x'
```
To avoid a breaking change, overload resolution will be updated to prefer strongly-typed delegates and expressions over `System.Delegate`.
_The example below demonstrates the tie-breaking rule for lambdas. Is there an equivalent example for method groups?_
```csharp
static void Execute(Expression<Func<string>> e) { }
static void Execute(Delegate d) { }
static string GetString() => "";
static int GetInt() => 0;
Execute(() => ""); // Execute(Expression<Func<string>>) [tie-breaker]
Execute(() => 0); // Execute(Delegate) [new]
Execute(GetString); // Execute(Delegate) [new]
Execute(GetInt); // Execute(Delegate) [new]
```
## Syntax
```antlr
lambda_expression
: attribute_list* modifier* lambda_parameters (':' type)? '=>' (block | body)
;
lambda_parameters
: lambda_parameter
| '(' (lambda_parameter (',' lambda_parameter)*)? ')'
;
lambda_parameter
: identifier
| (attribute_list* modifier* type)? identifier equals_value_clause?
;
```
_Does the `: type` return type syntax introduce ambiguities with `?:` that cannot be resolved easily?_
_Should we allow attributes on parameters without explicit types, such as `([MyAttribute] x) => { }`? (We don't allow modifiers on parameters without explicit types, such as `(ref x) => { }`.)_
## Design meetings
- https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-03-03.md
- https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-04-12.md

View file

@ -0,0 +1,224 @@
# List patterns on enumerables
## Summary
Lets you to match an enumerable with a sequence of patterns e.g. `enumerable is { 1, 2, 3 }` will match a sequence of the length three with 1, 2, 3 as its elements.
## Detailed design
The pattern syntax is unchanged:
```antlr
primary_pattern
: list_pattern
| length_pattern
| slice_pattern
| // all of the pattern forms previously defined
;
```
### Pattern compatibility
A *length_pattern* is now also compatible with any type that is not *countable* but is *enumerable* — it can be used in `foreach`.
A *list_pattern* is now also compatible with any type that is not *indexable* but is *enumerable*.
A *slice_pattern* without a subpattern is compatible with any type that is compatible with a *list_pattern*.
```
enumerable is { 1, 2, .. } // okay
enumerable is { 1, 2, ..var x } // error
```
### Semantics
If the input type is *enumerable* but not *countable*, then the *length_pattern* is checked on the number of elements obtained from enumerating the collection.
If the input type is *enumerable* but not *indexable*, then the *list_pattern* enumerates elements from the collection and checks them against the listed patterns:
Patterns at the start of the *list_pattern* — that are before the `..` *slice_pattern* if one is present, or all otherwise — are matched against the elements produced at the start of the enumeration.
If the collection does not produce enough elements to get a value corresponding to a starting pattern, the match fails. So the *constant_pattern* `3` in `{ 1, 2, 3, .. }` doesn't match when the collection has fewer than 3 elements.
Patterns at the end of the *list_pattern* (that are following the `..` *slice_pattern* if one is present) are matched against the elements produced at the end of the enumeration.
If the collection does not produce enough elements to get values corresponding to the ending patterns, the *slice_pattern* does not match. So the *slice_pattern* in `{ 1, .., 3 }` doesn't match when the collection has fewer than 2 elements.
A *list_pattern* without a *slice_pattern* only matches if the number of elements produced by complete enumeration and the number of patterns are equals. So `{ _, _, _ }` only matches when the collection produces exactly 3 elements.
Note that those implicit checks for number of elements in the collection are unaffected by the collection type being *countable*. So `{ _, _, _ }` will not make use of `Length` or `Count` even if one is available.
When multiple *list_patterns* are applied to one input value the collection will be enumerated once at most:
```
_ = collection switch
{
{ 1 } => ...,
{ 2 } => ...,
{ .., 3 } => ...,
};
_ = collectionContainer switch
{
{ Collection: { 1 } } => ...,
{ Collection: { 2 } } => ...,
{ Collection: { .., 3 } } => ...,
};
```
It is possible that the collection will not be completely enumerated. For example, if one of the patterns in the *list_pattern* doesn't match or when there are no ending patterns in a *list_pattern* (e.g. `collection is { 1, 2, .. }`).
If an enumerator is produced when a *list_pattern* is applied to an enumerable type and that enumerator is disposable it will be disposed when a top-level pattern containing the *list_pattern* successfully matches, or when none of the patterns match (in the case of a `switch` statement or expression). It is possible for an enumerator to be disposed more than once and the enumerator must ignore all calls to `Dispose` after the first one.
```
// any enumerator used to evaluate this switch statement is disposed at the indicated locations
_ = collection switch
{
{ 1 } => /* here */ ...,
_ => /* here */ ...,
};
/* here too, with a spilled try/finally around the switch expression */
```
### Lowering on enumerable type
> **Open question**: Need to investigate how to reduce allocation for the end circular buffer. `stackalloc` is bad in loops. Maybe we'll just have to fall back to locals and a `switch`. (see [`params` feature discussion](https://github.com/dotnet/csharplang/blob/main/proposals/format.md#extending-params) also)
Although a helper type is not necessary, it helps simplify and illustrate the logic.
```
class ListPatternHelper
{
// Notes:
// We could inline this logic to avoid creating a new type and to handle the pattern-based enumeration scenarios.
// We may only need one element in start buffer, or maybe none at all, if we can control the order of checks in the patterns DAG.
// We could emit a count check for a non-terminal `..` and economize on count checks a bit.
private EnumeratorType enumerator;
private int count;
private ElementType[] startBuffer;
private ElementType[] endCircularBuffer;
public ListPatternHelper(EnumerableType enumerable, int startPatternsCount, int endPatternsCount)
{
count = 0;
enumerator = enumerable.GetEnumerator();
startBuffer = startPatternsCount == 0 ? null : new ElementType[startPatternsCount];
endCircularBuffer = endPatternsCount == 0 ? null : new ElementType[endPatternsCount];
}
// targetIndex = -1 means we want to enumerate completely
private int MoveNextIfNeeded(int targetIndex)
{
int startSize = startBuffer?.Length ?? 0;
int endSize = endCircularBuffer?.Length ?? 0;
Debug.Assert(targetIndex == -1 || (targetIndex >= 0 && targetIndex < startSize));
while ((targetIndex == -1 || count <= targetIndex) && enumerator.MoveNext())
{
if (count < startSize)
startBuffer[count] = enumerator.Current;
if (endSize > 0)
endCircularBuffer[count % endSize] = enumerator.Current;
count++;
}
return count;
}
public bool Last()
{
return !enumerator.MoveNext();
}
public int Count()
{
return MoveNextIfNeeded(-1);
}
// fulfills the role of `[index]` for start elements when enough elements are available
public bool TryGetStartElement(int index, out ElementType value)
{
Debug.Assert(startBuffer is not null && index >= 0 && index < startBuffer.Length);
MoveNextIfNeeded(index);
if (count > index)
{
value = startBuffer[index];
return true;
}
value = default;
return false;
}
// fulfills the role of `[^hatIndex]` for end elements when enough elements are available
public ElementType GetEndElement(int hatIndex)
{
Debug.Assert(endCircularBuffer is not null && hatIndex > 0 && hatIndex <= endCircularBuffer.Length);
int endSize = endCircularBuffer.Length;
Debug.Assert(endSize > 0);
return endCircularBuffer[(count - hatIndex) % endSize];
}
}
```
`collection is [3]` is lowered to
```
@{
var helper = new ListPatternHelper(collection, 0, 0);
helper.Count() == 3
}
```
`collection is { 0, 1 }` is lowered to
```
@{
var helper = new ListPatternHelper(collection, 2, 0);
helper.TryGetStartElement(index: 0, out var element0) && element0 is 0 &&
helper.TryGetStartElement(1, out var element1) && element1 is 1 &&
helper.Last()
}
```
`collection is { 0, 1, .. }` is lowered to
```
@{
var helper = new ListPatternHelper(collection, 2, 0);
helper.TryGetStartElement(index: 0, out var element0) && element0 is 0 &&
helper.TryGetStartElement(1, out var element1) && element1 is 1
}
```
`collection is { .., 3, 4 }` is lowered to
```
@{
var helper = new ListPatternHelper(collection, 0, 2);
helper.Count() >= 2 && // `..` with 2 ending patterns
helper.GetEndElement(hatIndex: 2) is 3 && // [^2] is 3
helper.GetEndElement(1) is 4 // [^1] is 4
}
```
`collection is { 1, 2, .., 3, 4 }` is lowered to
```
@{
var helper = new ListPatternHelper(collection, 2, 2);
helper.TryGetStartElement(index: 0, out var element0) && element0 is 1 &&
helper.TryGetStartElement(1, out var element1) && element1 is 2 &&
helper.Count() >= 4 && // `..` with 2 starting patterns and 2 ending patterns
helper.GetEndElement(hatIndex: 2) is 3 &&
helper.GetEndElement(1) is 4
}
```
The same way that a `Type { name: pattern }` *property_pattern* checks that the input has the expected type and isn't null before using that as receiver for the property checks, so can we have the `{ ..., ... }` *list_pattern* initialize a helper and use that as the pseudo-receiver for element accesses.
This should allow merging branches of the patterns DAG, thus avoiding creating multiple enumerators.
Note: async enumerables are out-of-scope for C# 10. (Confirmed in LDM 4/12/2021)
Note: sub-patterns are disallowed in slice-patterns on enumerables for now despite some desirable uses: `e is { 1, 2, ..[var count] }` (LDM 4/12/2021)
## Unresolved questions
1. Should we limit the list-pattern to `IEnumerable` types? Then we could allow `{ 1, 2, ..var x }` (`x` would be an `IEnumerable` we would cook up) (answer [LDM 4/12/2021]: no, we'll disallow sub-pattern in slice pattern on enumerable for now)
2. Should we try and optimize list-patterns like `{ 1, _, _ }` on a countable enumerable type? We could just check the first enumerated element then check `Length`/`Count`. Can we assume that `Count` agrees with enumerated count?
3. Should we try to cut the enumeration short for length-patterns on enumerables in some cases? (computing min/max acceptable count and checking partial count against that)
What if the enumerable type has some sort of `TryGetNonEnumeratedCount` API?
4. Can we detect at runtime that the input type is sliceable, so as to avoid enumeration? .NET 6 may be adding some LINQ methods/extensions that would help.

View file

@ -2,138 +2,45 @@
## Summary
Lets you to match an array or a list with a sequence of patterns e.g. `array is {1, 2, 3}` will match an integer array of the length three with 1, 2, 3 as its elements, respectively.
Lets you to match an array or a list with a sequence of patterns e.g. `array is [1, 2, 3]` will match an integer array of the length three with 1, 2, 3 as its elements, respectively.
## Detailed design
The pattern syntax is modified as follow:
```antlr
positional_pattern
: type? positional_pattern_clause length_pattern_clause? property_or_list_pattern_clause? simple_designation?
;
property_or_list_pattern_clause
: list_pattern_clause
| property_pattern_clause
;
property_pattern_clause
: '{' (subpattern (',' subpattern)* ','?)? '}'
;
list_pattern_clause
: '{' pattern (',' pattern)* ','? '}'
;
length_pattern_clause
: '[' pattern ']'
;
length_pattern
: type? length_pattern_clause property_or_list_pattern_clause? simple_designation?
: '[' (pattern (',' pattern)* ','?)? ']'
;
list_pattern
: type? list_pattern_clause simple_designation?
;
property_pattern
: type? property_pattern_clause simple_designation?
: list_pattern_clause simple_designation?
;
slice_pattern
: '..' negated_pattern?
: '..' pattern?
;
primary_pattern
: list_pattern
| length_pattern
| slice_pattern
| // all of the pattern forms previously defined
;
```
There are three new patterns:
There are two new patterns:
- The *list_pattern* is used to match elements.
- The *length_pattern* is used to match the length.
- A *slice_pattern* is only permitted once and only directly in a *list_pattern_clause* and discards _**zero or more**_ elements.
> **Open question**: Should we accept a general *pattern* following `..` in a *slice_pattern*?
Notes:
- Due to the ambiguity with *property_pattern*, a *list_pattern* cannot be empty and a *length_pattern* should be used instead to match a list with the length of zero, e.g. `[0]`.
- The *length_pattern_clause* must be in agreement with the inferred length from the *list_pattern_clause* (if any), e.g. `[0] {1}` is an error.
- However, `[1] {}` is **not** an error due to the length mismatch, rather, `{}` would be always parsed as an empty *property_pattern_clause*. We may want to add a warning for it so it would not be confused that way.
- If the *type* is an *array_type*, the *length_pattern_clause* is disambiguated so that `int[] [0]` would match an empty integer array.
- All other combinations are valid, for instance `T (p0, p1) [p2] { name: p3 } v` or `T (p0, p1) [p2] { p3 } v` where each clause can be omitted.
> **Open question**: Should we support all these combinations?
#### Pattern compatibility
A *length_pattern* is compatible with any type that is *countable* — it has an accessible property getter that returns an `int` and has the name `Length` or `Count`. If both properties are present, the former is preferred.
A *length_pattern* is also compatible with any type that is *enumerable* — it can be used in `foreach`.
A *list_pattern* is compatible with any type that is *countable* as well as *indexable* — it has an accessible indexer that takes an `Index` as an argument or otherwise an accessible indexer with a single `int` parameter. If both indexers are present, the former is preferred.
A *list_pattern* is compatible with any type that is *countable* as well as *indexable* — it has an accessible indexer that takes an `Index` or `int` argument. If both indexers are present, the former is preferred.
A *list_pattern* is also compatible with any type that is *enumerable*.
A *slice_pattern* with a subpattern is compatible with any type that is *countable* as well as *sliceable* — it has an accessible indexer that takes a `Range` as an argument or otherwise an accessible `Slice` method with two `int` parameters. If both are present, the former is preferred.
A *slice_pattern* is compatible with any type that is *countable* as well as *sliceable* — it has an accessible indexer that takes a `Range` argument or otherwise an accessible `Slice` method that takes two `int` arguments. If both are present, the former is preferred.
A *slice_pattern* without a subpattern is also compatible with any type that is *enumerable*.
A *slice_pattern* without a subpattern is compatible with any type that is compatible with a *list_pattern*.
```
enumerable is { 1, 2, .. } // okay
enumerable is { 1, 2, ..var x } // error
```
This set of rules is derived from the [***range indexer pattern***](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/ranges.md#implicit-index-support) but relaxed to ignore optional or `params` parameters, if any.
> **Open question**: We should define the exact binding rules for any of these members and decide if we want to diverge from the range spec.
> **Open question**: Should extension methods play a role in sliceability?
#### Semantics on enumerable type
If the input type is *enumerable* but not *countable*, then the *length_pattern* is checked on the number of elements obtained from enumerating the collection.
If the input type is *enumerable* but not *indexable*, then the *list_pattern* enumerates elements from the collection and checks them against the listed patterns:
Patterns at the start of the *list_pattern* — that are before the `..` *slice_pattern* if one is present, or all otherwise — are matched against the elements produced at the start of the enumeration.
If the collection does not produce enough elements to get a value corresponding to a starting pattern, the match fails. So the *constant_pattern* `3` in `{ 1, 2, 3, .. }` doesn't match when the collection has fewer than 3 elements.
Patterns at the end of the *list_pattern* (that are following the `..` *slice_pattern* if one is present) are matched against the elements produced at the end of the enumeration.
If the collection does not produce enough elements to get values corresponding to the ending patterns, the *slice_pattern* does not match. So the *slice_pattern* in `{ 1, .., 3 }` doesn't match when the collection has fewer than 2 elements.
A *list_pattern* without a *slice_pattern* only matches if the number of elements produced by complete enumeration and the number of patterns are equals. So `{ _, _, _ }` only matches when the collection produces exactly 3 elements.
Note that those implicit checks for number of elements in the collection are unaffected by the collection type being *countable*. So `{ _, _, _ }` will not make use of `Length` or `Count` even if one is available.
When multiple *list_patterns* are applied to one input value the collection will be enumerated once at most:
```
_ = collection switch
{
{ 1 } => ...,
{ 2 } => ...,
{ .., 3 } => ...,
};
_ = collectionContainer switch
{
{ Collection: { 1 } } => ...,
{ Collection: { 2 } } => ...,
{ Collection: { .., 3 } } => ...,
};
```
It is possible that the collection will not be completely enumerated. For example, if one of the patterns in the *list_pattern* doesn't match or when there are no ending patterns in a *list_pattern* (e.g. `collection is { 1, 2, .. }`).
If an enumerator is produced when a *list_pattern* is applied to an enumerable type and that enumerator is disposable it will be disposed when a top-level pattern containing the *list_pattern* successfully matches, or when none of the patterns match (in the case of a `switch` statement or expression). It is possible for an enumerator to be disposed more than once and the enumerator must ignore all calls to `Dispose` after the first one.
```
// any enumerator used to evaluate this switch statement is disposed at the indicated locations
_ = collection switch
{
{ 1 } => /* here */ ...,
_ => /* here */ ...,
};
/* here too, with a spilled try/finally around the switch expression */
```
This set of rules is derived from the [***range indexer pattern***](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/ranges.md#implicit-index-support).
#### Subsumption checking
@ -142,29 +49,43 @@ Subsumption checking works just like [positional patterns with `ITuple`](https:/
For example, the following code produces an error because both patterns yield the same DAG:
```cs
case {_, .., 1}: // expr.Length is >= 2 && expr[^1] is 1
case {.., _, 1}: // expr.Length is >= 2 && expr[^1] is 1
case [_, .., 1]: // expr.Length is >= 2 && expr[^1] is 1
case [.., _, 1]: // expr.Length is >= 2 && expr[^1] is 1
```
Unlike:
```cs
case {_, 1, ..}: // expr.Length is >= 2 && expr[1] is 1
case {.., 1, _}: // expr.Length is >= 2 && expr[^2] is 1
case [_, 1, ..]: // expr.Length is >= 2 && expr[1] is 1
case [.., 1, _]: // expr.Length is >= 2 && expr[^2] is 1
```
The order in which subpatterns are matched at runtime is unspecified, and a failed match may not attempt to match all subpatterns.
> **Open question**: The pattern `{..}` tests for `expr.Length >= 0`. Should we omit such test (assuming `Length` is always non-negative)?
#### Lowering on countable/indexeable/sliceable type
Given a specific length, it's possible that two subpatterns refer to the same element, in which case a test for this value is inserted into the decision DAG.
A pattern of the form `expr is {1, 2, 3}` is equivalent to the following code (if compatible via implicit `Index` support):
- For instance, `[_, >0, ..] or [.., <=0, _]` becomes `length >= 2 && ([1] > 0 || length == 3 || [^2] <= 0)` where the length value of 3 implies the other test.
- Conversely, `[_, >0, ..] and [.., <=0, _]` becomes `length >= 2 && [1] > 0 && length != 3 && [^2] <= 0` where the length value of 3 disallows the other test.
As a result, an error is produced for something like `case [.., p]: case [p]:` because at runtime, we're matching the same element in the second case.
If a slice subpattern matches a list or a length value, subpatterns are treated as if they were a direct subpattern of the containing list. For instance, `[..[1, 2, 3]]` subsumes a pattern of the form `[1, 2, 3]`.
The following assumptions are made on the members being used:
- The property that makes the type *countable* is assumed to always return a non-negative value, if and only if the type is *indexable*. For instance, the pattern `{ Length: -1 }` can never match an array.
- The member that makes the type *sliceable* is assumed to be well-behaved, that is, the return value is never null and that it is a proper subslice of the containing list.
The behavior of a pattern-matching operation is undefined if any of the above assumptions doesn't hold.
#### Lowering
A pattern of the form `expr is [1, 2, 3]` is equivalent to the following code (if compatible via implicit `Index` support):
```cs
expr.Length is 3
&& expr[0] is 1
&& expr[1] is 2
&& expr[2] is 3
```
A *slice_pattern* acts like a proper discard i.e. no tests will be emitted for such pattern, rather it only affects other nodes, namely the length and indexer. For instance, a pattern of the form `expr is {1, .. var s, 3}` is equivalent to the following code (if compatible via explicit `Index` and `Range` support):
A *slice_pattern* acts like a proper discard i.e. no tests will be emitted for such pattern, rather it only affects other nodes, namely the length and indexer. For instance, a pattern of the form `expr is [1, .. var s, 3]` is equivalent to the following code (if compatible via explicit `Index` and `Range` support):
```cs
expr.Length is >= 2
&& expr[new Index(0)] is 1
@ -173,166 +94,8 @@ expr.Length is >= 2
```
The *input type* for the *slice_pattern* is the return type of the underlying `this[Range]` or `Slice` method with two exceptions: For `string` and arrays, `string.Substring` and `RuntimeHelpers.GetSubArray` will be used, respectively.
#### Lowering on enumerable type
> **Open question**: Need to investigate how to reduce allocation for the end circular buffer. `stackalloc` is bad in loops. Maybe we'll just have to fall back to locals and a `switch`. (see [`params` feature discussion](https://github.com/dotnet/csharplang/blob/main/proposals/format.md#extending-params) also)
Although a helper type is not necessary, it helps simplify and illustrate the logic.
```
class ListPatternHelper
{
// Notes:
// We could inline this logic to avoid creating a new type and to handle the pattern-based enumeration scenarios.
// We may only need one element in start buffer, or maybe none at all, if we can control the order of checks in the patterns DAG.
// We could emit a count check for a non-terminal `..` and economize on count checks a bit.
private EnumeratorType enumerator;
private int count;
private ElementType[] startBuffer;
private ElementType[] endCircularBuffer;
public ListPatternHelper(EnumerableType enumerable, int startPatternsCount, int endPatternsCount)
{
count = 0;
enumerator = enumerable.GetEnumerator();
startBuffer = startPatternsCount == 0 ? null : new ElementType[startPatternsCount];
endCircularBuffer = endPatternsCount == 0 ? null : new ElementType[endPatternsCount];
}
// targetIndex = -1 means we want to enumerate completely
private int MoveNextIfNeeded(int targetIndex)
{
int startSize = startBuffer?.Length ?? 0;
int endSize = endCircularBuffer?.Length ?? 0;
Debug.Assert(targetIndex == -1 || (targetIndex >= 0 && targetIndex < startSize));
while ((targetIndex == -1 || count <= targetIndex) && enumerator.MoveNext())
{
if (count < startSize)
startBuffer[count] = enumerator.Current;
if (endSize > 0)
endCircularBuffer[count % endSize] = enumerator.Current;
count++;
}
return count;
}
public bool Last()
{
return !enumerator.MoveNext();
}
public int Count()
{
return MoveNextIfNeeded(-1);
}
// fulfills the role of `[index]` for start elements when enough elements are available
public bool TryGetStartElement(int index, out ElementType value)
{
Debug.Assert(startBuffer is not null && index >= 0 && index < startBuffer.Length);
MoveNextIfNeeded(index);
if (count > index)
{
value = startBuffer[index];
return true;
}
value = default;
return false;
}
// fulfills the role of `[^hatIndex]` for end elements when enough elements are available
public ElementType GetEndElement(int hatIndex)
{
Debug.Assert(endCircularBuffer is not null && hatIndex > 0 && hatIndex <= endCircularBuffer.Length);
int endSize = endCircularBuffer.Length;
Debug.Assert(endSize > 0);
return endCircularBuffer[(count - hatIndex) % endSize];
}
}
```
`collection is [3]` is lowered to
```
@{
var helper = new ListPatternHelper(collection, 0, 0);
helper.Count() == 3
}
```
`collection is { 0, 1 }` is lowered to
```
@{
var helper = new ListPatternHelper(collection, 2, 0);
helper.TryGetStartElement(index: 0, out var element0) && element0 is 0 &&
helper.TryGetStartElement(1, out var element1) && element1 is 1 &&
helper.Last()
}
```
`collection is { 0, 1, .. }` is lowered to
```
@{
var helper = new ListPatternHelper(collection, 2, 0);
helper.TryGetStartElement(index: 0, out var element0) && element0 is 0 &&
helper.TryGetStartElement(1, out var element1) && element1 is 1
}
```
`collection is { .., 3, 4 }` is lowered to
```
@{
var helper = new ListPatternHelper(collection, 0, 2);
helper.Count() >= 2 && // `..` with 2 ending patterns
helper.GetEndElement(hatIndex: 2) is 3 && // [^2] is 3
helper.GetEndElement(1) is 4 // [^1] is 4
}
```
`collection is { 1, 2, .., 3, 4 }` is lowered to
```
@{
var helper = new ListPatternHelper(collection, 2, 2);
helper.TryGetStartElement(index: 0, out var element0) && element0 is 1 &&
helper.TryGetStartElement(1, out var element1) && element1 is 2 &&
helper.Count() >= 4 && // `..` with 2 starting patterns and 2 ending patterns
helper.GetEndElement(hatIndex: 2) is 3 &&
helper.GetEndElement(1) is 4
}
```
The same way that a `Type { name: pattern }` *property_pattern* checks that the input has the expected type and isn't null before using that as receiver for the property checks, so can we have the `{ ..., ... }` *list_pattern* initialize a helper and use that as the pseudo-receiver for element accesses.
This should allow merging branches of the patterns DAG, thus avoiding creating multiple enumerators.
Note: async enumerables are out-of-scope. (Confirmed in LDM 4/12/2021)
Note: sub-patterns are disallowed in list-patterns on enumerables for now despite some desirable uses: `e is { 1, 2, ..[var count] }` (LDM 4/12/2021)
### Additional types
Beyond the pattern-based mechanism outlined above, there are an additional two set of types that can be covered as a special case.
- **Multi-dimensional arrays**: All nested list patterns must agree to a length range.
- **Foreach-able types**: This includes pattern-based and extension `GetEnumerator`.
A slice subpattern (i.e. the pattern following `..` in a *slice_pattern*) is disallowed for either of the above.
## Unresolved questions
All multi-dimensional arrays can be non-zero-based. We can either:
1. Add a runtime helper to check if the array is zero-based across all dimensions.
2. Call `GetLowerBound` and add it to each indexer access to pass the *correct* index.
3. Assume all arrays are zero-based since that's the default for arrays created by `new` expressions.
4. Should we limit the list-pattern to `IEnumerable` types? Then we could allow `{ 1, 2, ..var x }` (`x` would be an `IEnumerable` we would cook up) (answer [LDM 4/12/2021]: no, we'll disallow sub-pattern in slice pattern on enumerable for now)
5. Should we try and optimize list-patterns like `{ 1, _, _ }` on a countable enumerable type? We could just check the first enumerated element then check `Length`/`Count`. Can we assume that `Count` agrees with enumerated count?
6. Should we try to cut the enumeration short for length-patterns on enumerables in some cases? (computing min/max acceptable count and checking partial count against that)
What if the enumerable type has some sort of `TryGetNonEnumeratedCount` API?
7. Can we detect at runtime that the input type is sliceable, so as to avoid enumeration? .NET 6 may be adding some LINQ methods/extensions that would help.
1. Should we support multi-dimensional arrays? (answer [LDM 2021-05-26]: Not supported. If we want to make a general MD-array focused release, we would want to revisit all the areas they're currently lacking, not just list patterns.)
2. Should we accept a general *pattern* following `..` in a *slice_pattern*? (answer [LDM 2021-05-26]: Yes, any pattern is allowed after a slice.)
3. By this definition, the pattern `[..]` tests for `expr.Length >= 0`. Should we omit such test, assuming `Length` is always non-negative? (answer [LDM 2021-05-26]: `[..]` will not emit a Length check)

View file

@ -3,9 +3,9 @@ Low Level Struct Improvements
## Summary
This proposal is an aggregation of several different proposals for `struct`
performance improvements. The goal being a design which takes into account the
various proposals to create a single overarching feature set for `struct`
improvements.
performance improvements: ref fields and attributes to override lifetime defaults.
The goal being a design which takes into account the various proposals to create
a single overarching feature set for `struct` improvements.
## Motivation
Over the last few releases C# has added a number of low level performance
@ -71,7 +71,7 @@ once `ref` fields are supported.
// language
readonly ref struct Span<T>
{
ref readonly T _field;
readonly ref T _field;
int _length;
// This constructor does not exist today however will be added as a
@ -80,7 +80,7 @@ readonly ref struct Span<T>
// requires unsafe code.
public Span(ref T value)
{
ref _field = ref value;
_field = ref value;
_length = 1;
}
}
@ -214,12 +214,12 @@ ref struct RS
public RS(int[] array, int index)
{
ref _field = ref array[index];
_field = ref array[index];
}
public RS(ref int i)
{
ref _field = ref i;
_field = ref i;
}
static RS CreateRS(ref int i)
@ -269,7 +269,7 @@ ref struct RS1
ref int _field;
public RS1(ref int p)
{
ref _field = ref p;
_field = ref p;
}
}
@ -450,12 +450,12 @@ ref struct RS
public RS(int[] array)
{
ref _refField = ref array[0];
_refField = ref array[0];
}
public RS(ref int i)
{
ref _refField = ref i;
_refField = ref i;
}
public RS CreateRS() => ...;
@ -529,7 +529,7 @@ ref struct SmallSpan
// The *ref-safe-to-escape* of 'i' is the same as the *safe-to-escape*
// of 's' hence most assignment rules would allow it.
ref s._field = ref i;
s._field = ref i;
// ERROR: this must be disallowed for the exact same reasons we can't
// return a Span<T> wrapping the parameter: the consumption rules
@ -545,7 +545,7 @@ ref struct SmallSpan
// Okay: the value being assigned here is known to refer to the heap
// hence it is allowed by our rules above because it requires no changes
// to existing method invocation rules (hence preserves compat)
ref s._field = ref array[i];
s._field = ref array[i];
return s;
}
@ -568,7 +568,7 @@ restricted to the current method. Such a design is discussed [here](https://gith
However extra complexity of such rules do not seem to be worth the limited cases
this enables. Should compelling samples come up we can revisit this decision.
This means though that `ref` fields are largely in practice `ref readonly`. The
This means though that `ref` fields are largely in practice `readonly ref`. The
main exceptions being object initializers and when the value is known to refer
to the heap.
@ -624,7 +624,7 @@ Misc Notes:
type.
- The reference assembly generation process must preserve the presence of a
`ref` field inside a `ref struct`
- A `ref readonly struct` must declare its `ref` fields as `ref readonly`
- A `readonly ref struct` must declare its `ref` fields as `readonly ref`
- The span safety rules for constructors, fields and assignment must be updated
as outlined in this document.
- The span safety rules need to include the definition of `ref` values that
@ -1222,7 +1222,7 @@ ref struct StackLinkedListNode<T>
public StackLinkedListNode(T value, ref StackLinkedListNode<T> next)
{
_value = value;
ref _next = ref next;
_next = ref next;
}
}
```

View file

@ -0,0 +1,40 @@
# Allow new-lines in all interpolations
* [x] Proposed
* [x] Implementation: https://github.com/dotnet/roslyn/pull/56853
* [x] Specification: this file.
## Summary
[summary]: #summary
The language today non-verbatim and verbatim interpolated strings (`$""` and `$@""` respectively). The primary *sensible* difference for these is that a non-verbatim interpolated string works like a normal string and cannot contain newlines in its text segments, and must instead use escapes (like `\r\n`). Conversely, a verbatim interpolated string can contain newlines in its text segments (like a verbatim string), and doesn't escape newlines or other character (except for `""` to escape a quote itself).
This is all reasonable and will not change with this proposal.
What is unreasonable today is that we extend the restriction on 'no newlines' in a non-verbatim interpolated string *beyond* its text segments into the *interpolations* themselves. This means, for example, that you cannot write the following:
```c#
var v = $"Count is\t: { this.Is.A.Really(long(expr))
.That.I.Should(
be + able)[
to.Wrap()] }.";
```
Ultimately, the 'interpolation must be on a single line itself' rule is just a restriction of the current implementation. That restriction really isn't necessary, and can be annoying, and would be fairly trivial to remove (see work https://github.com/dotnet/roslyn/pull/54875 to show how). In the end, all it does is force the dev to place things on a single line, or force them into a verbatim interpolated string (both of which may be unpalatable).
The interpolation expressions themselves are not text, and shouldn't be beholden to any escaping/newline rules therin.
## Specification change
```diff
single_regular_balanced_text_character
- : '<Any character except / (U+002F), @ (U+0040), \" (U+0022), $ (U+0024), ( (U+0028), ) (U+0029), [ (U+005B), ] (U+005D), { (U+007B), } (U+007D) and new_line_character>'
- | '</ (U+002F), if not directly followed by / (U+002F) or * (U+002A)>'
+ : <Any character except @ (U+0040), \" (U+0022), $ (U+0024), ( (U+0028), ) (U+0029), [ (U+005B), ] (U+005D), { (U+007B), } (U+007D)>
+ | comment
;
```
## LDM Discussions
https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-09-20.md

View file

@ -0,0 +1,88 @@
# Primary constructors in non-record classes and structs
* [x] Proposed
* [ ] Prototype: Not started
* [ ] Implementation: Not started
* [ ] Specification: Not started
## Summary
[summary]: #summary
Primary constructors, currently only available on [record types](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/records.md#primary-constructor), will be generalized to non-record classes and structs. They have the following differences in behavior from primary constructors in records:
- Instead of public members, a private field is generated for each parameter of the primary constructor.
- If the field is unreferenced (as a field) within the body of the class or struct declaration, it is not emitted. (The parameter can still be used in e.g. initializers).
- No corresponding deconstructor is generated.
## Motivation
[motivation]: #motivation
The ability of a class or struct in C# to have more than one constructor provides for generality, but at the expense of some tedium in the declaration syntax, because the constructor input and the class state need to be cleanly separated.
Primary constructors put the parameters of one constructor in scope for the whole class to be used for initialization or directly as object state. The trade-off is that any other constructors must call through the primary constructor.
``` c#
public class C(int i, string s)
{
public int I { get; set; } = i; // i used for initialization
public string S // s used directly in function members
{
get => s;
set => s = value ?? throw new NullArgumentException(nameof(X));
}
}
```
## Detailed design
[design]: #detailed-design
*Note*: Any similarity with the specification of [primary constructors in records](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/records.md#primary-constructor) is entirely intentional.
Class and struct declarations are augmented to allow a parameter list on the type name and and argument list on the base class:
``` antlr
class_declaration
: attributes? class_modifier* 'partial'? 'class' identifier type_parameter_list?
parameter_list? class_base? type_parameter_constraints_clause* class_body ';'?
;
class_base
: ':' class_type argument_list?
| ':' interface_type_list
| ':' class_type argument_list? ',' interface_type_list
;
struct_declaration
: attributes? struct_modifier* 'partial'? 'struct' identifier type_parameter_list?
parameter_list? struct_interfaces? type_parameter_constraints_clause* struct_body ';'?
;
```
It is an error for a `class_base` to have an `argument_list` if the enclosing `class_declaration` does not contain a `parameter_list`. At most one partial type declaration of a partial class or struct may provide a `parameter_list`. The parameters in the `parameter_list` must all be value parameters.
A class or struct with a `parameter_list` has an implicit public constructor whose signature corresponds to the value parameters of the type declaration. This is called the ***primary constructor*** for the type, and causes the implicitly declared parameterless constructor, if present, to be suppressed. It is an error to have a primary constructor and a constructor with the same signature already present in the type declaration.
At runtime the primary constructor
1. executes the instance initializers appearing in the class or struct body
2. invokes the base class constructor with the arguments provided in the `class_base` clause, if present
If a class or struct has a primary constructor, any user-defined constructor, except "copy constructor" must have an explicit `this` constructor initializer.
Parameters of the primary constructor as well as members of the record are in scope within the `argument_list`
of the `class_base` clause and within initializers of instance fields or properties. Instance members would
be an error in these locations (similar to how instance members are in scope in regular constructor initializers today, but an error to use), but the parameters of the primary constructor would be in scope and useable and would shadow members. Static members would also be usable, similar to how base calls and initializers work in ordinary constructors today.
A warning is produced if a parameter of the primary constructor is not read.
Expression variables declared in the `argument_list` are in scope within the `argument_list`. The same shadowing rules as within an argument list of a regular constructor initializer apply.
For each parameter in the `parameter_list`, if the type declaration does not directly contain a property or field declaration of the same name as the parameter, and if any expression within the body of the type declaration would reference such a member, then a private field is implicitly declared with the same name and type as the parameter.
The field is initialized to the value of the corresponding primary constructor parameter. Attributes can be applied to the synthesized field by using `field:` targets for attributes syntactically applied to the corresponding record parameter.
Unlike primary constructors in records, no deconstructor is generated.

View file

@ -1,4 +1,4 @@
# Simplified Null Argument Checking
# Parameter Null Checking
## Summary
This proposal provides a simplified syntax for validating method arguments are not `null` and throwing
@ -16,17 +16,17 @@ of it. The syntax can be used independent of `#nullable` directives.
## Detailed Design
### Null validation parameter syntax
The bang operator, `!`, can be positioned after a parameter name in a parameter list and this will cause the C#
compiler to emit standard `null` checking code for that parameter. This is referred to as `null` validation parameter
The bang-bang operator, `!!`, can be positioned after a parameter name in a parameter list and this will cause the C#
compiler to emit `null` checking code for that parameter. This is referred to as `null` validation parameter
syntax. For example:
``` csharp
void M(string name!) {
void M(string name!!) {
...
}
```
Will be translated into:
Will be translated into code similar to the following:
``` csharp
void M(string name) {
@ -37,8 +37,12 @@ void M(string name) {
}
```
The implementation behavior must be that if the parameter is null, it creates and throws an `ArgumentNullException` with the parameter name as a constructor argument. The implementation is free to use any strategy that achieves this. This could result in observable differences between different compliant implementations, such as whether calls to helper methods are present above the call to the method with the null-checked parameter in the exception stack trace.
We make these allowances because parameter null checks are used frequently in libraries with tight performance and size constraints. For example, to optimize code size, inlining, etc., the implementation may use helper methods to perform the null check a la the [ArgumentNullException.ThrowIfNull](https://github.com/dotnet/runtime/blob/1d08e154b942a41e72cbe044e01fff8b13c74496/src/libraries/System.Private.CoreLib/src/System/ArgumentNullException.cs#L56-L69) methods.
The generated `null` check will occur before any developer authored code in the method. When multiple parameters contain
the `!` operator then the checks will occur in the same order as the parameters are declared.
the `!!` operator then the checks will occur in the same order as the parameters are declared.
``` csharp
void M(string p1, string p2) {
@ -53,12 +57,12 @@ void M(string p1, string p2) {
```
The check will be specifically for reference equality to `null`, it does not invoke `==` or any user defined operators.
This also means the `!` operator can only be added to parameters whose type can be tested for equality against `null`.
This also means the `!!` operator can only be added to parameters whose type can be tested for equality against `null`.
This means it can't be used on a parameter whose type is known to be a value type.
``` csharp
// Error: Cannot use ! on parameters who types derive from System.ValueType
void G<T>(T arg!) where T : struct {
// Error: Cannot use !! on parameters who types derive from System.ValueType
void G<T>(T arg!!) where T : struct {
}
```
@ -73,7 +77,7 @@ For example:
``` csharp
class C {
string field = GetString();
C(string name!): this(name) {
C(string name!!): this(name) {
...
}
}
@ -101,7 +105,7 @@ parameter syntax that lacks parens.
``` csharp
void G() {
// An identity lambda which throws on a null input
Func<string, string> s = x! => x;
Func<string, string> s = x!! => x;
}
```
@ -111,7 +115,7 @@ or `async` iterators.
``` csharp
class Iterators {
IEnumerable<char> GetCharacters(string s!) {
IEnumerable<char> GetCharacters(string s!!) {
foreach (var c in s) {
yield return c;
}
@ -124,7 +128,7 @@ class Iterators {
}
```
The `!` operator can only be used for parameter lists which have an associated method body. This
The `!!` operator can only be used for parameter lists which have an associated method body. This
means it cannot be used in an `abstract` method, `interface`, `delegate` or `partial` method
definition.
@ -153,16 +157,16 @@ is instantiated as a value type the code will be evaluated as `false`. For cases
code will do a proper `is null` check.
### Intersection with Nullable Reference Types
Any parameter which has a `!` operator applied to it's name will start with the nullable state being not `null`. This is
Any parameter which has a `!!` operator applied to it's name will start with the nullable state being not `null`. This is
true even if the type of the parameter itself is potentially `null`. That can occur with an explicitly nullable type,
such as say `string?`, or with an unconstrained type parameter.
When a `!` syntax on parameters is combined with an explicitly nullable type on the parameter then a warning will
When a `!!` syntax on parameters is combined with an explicitly nullable type on the parameter then a warning will
be issued by the compiler:
``` csharp
void WarnCase<T>(
string? name!, // Warning: combining explicit null checking with a nullable type
string? name!!, // Warning: combining explicit null checking with a nullable type
T value1 // Okay
)
```
@ -174,7 +178,7 @@ None
### Constructors
The code generation for constructors means there is a small, but observable, behavior change when moving from standard
`null` validation today and the `null` validation parameter syntax (`!`). The `null` check in standard validation
`null` validation today and the `null` validation parameter syntax (`!!`). The `null` check in standard validation
occurs after both field initializers and any `base` or `this` calls. This means a developer can't necessarily migrate
100% of their `null` validation to the new syntax. Constructors at least require some inspection.
@ -183,7 +187,7 @@ logical that the `null` check run before any logic in the constructor does. Can
are discovered.
### Warning when mixing ? and !
There was a lengthy discussion on whether or not a warning should be issued when the `!` syntax is applied to a
There was a lengthy discussion on whether or not a warning should be issued when the `!!` syntax is applied to a
parameter which is explicitly typed to a nullable type. On the surface it seems like a nonsensical declaration by
the developer but there are cases where type hierarchies could force developers into such a situation.
@ -203,7 +207,7 @@ abstract class C2 : C1 {
// Assembly3
abstract class C3 : C2 {
protected override void M(object o!) {
protected override void M(object o!!) {
...
}
}
@ -232,7 +236,7 @@ following to eliminate it:
``` csharp
// Assembly3
abstract class C3 : C2 {
protected override void M(object? o!) {
protected override void M(object? o!!) {
...
}
}
@ -241,15 +245,15 @@ abstract class C3 : C2 {
At this point the author of Assembly3 has a few choices:
- They can accept / suppress the warning about `object?` and `object` mismatch.
- They can accept / suppress the warning about `object?` and `!` mismatch.
- They can just remove the `null` validation check (delete `!` and do explicit checking)
- They can accept / suppress the warning about `object?` and `!!` mismatch.
- They can just remove the `null` validation check (delete `!!` and do explicit checking)
This is a real scenario but for now the idea is to move forward with the warning. If it turns out the warning happens
more frequently than we anticipate then we can remove it later (the reverse is not true).
### Implicit property setter arguments
The `value` argument of a parameter is implicit and does not appear in any parameter list. That means it cannot be a
target of this feature. The property setter syntax could be extended to include a parameter list to allow the `!`
target of this feature. The property setter syntax could be extended to include a parameter list to allow the `!!`
operator to be applied. But that cuts against the idea of this feature making `null` validation simpler. As such the
implicit `value` argument just won't work with this feature.

View file

@ -1,242 +0,0 @@
# Parameterless struct constructors
## Summary
Support parameterless constructors and instance field initializers for struct types.
## Motivation
Explicit parameterless constructors would give more control over minimally constructed instances of the struct type.
Instance field initializers would allow simplified initialization across multiple constructors.
Together these would close an obvious gap between `struct` and `class` declarations.
Support for field initializers would also allow initialization of fields in `record struct` declarations without explicitly implementing the primary constructor.
```csharp
record struct Person(string Name)
{
public object Id { get; init; } = GetNextId();
}
```
If struct field initializers are supported for constructors with parameters, it seems natural to extend that to parameterless constructors as well.
```csharp
record struct Person()
{
public string Name { get; init; }
public object Id { get; init; } = GetNextId();
}
```
## Proposal
### Instance field initializers
Instance field declarations for a struct may include initializers.
As with [class field initializers](https://github.com/dotnet/csharplang/blob/master/spec/classes.md#instance-field-initialization):
> A variable initializer for an instance field cannot reference the instance being created.
### Constructors
A struct may declare a parameterless instance constructor.
A parameterless instance constructor is valid for all struct kinds including `struct`, `readonly struct`, `ref struct`, and `record struct`.
If the struct does not declare a parameterless instance constructor, and the struct has no fields with variable initializers, the struct (see [struct constructors](https://github.com/dotnet/csharplang/blob/master/spec/structs.md#constructors)) ...
> implicitly has a parameterless instance constructor which always returns the value that results from setting all value type fields to their default value and all reference type fields to null.
If the struct does not declare a parameterless instance constructor, and the struct has field initializers, a `public` parameterless instance constructor is synthesized.
The parameterless constructor is synthesized even if all initializer values are zeros.
### Modifiers
A parameterless instance constructor may be less accessible than the containing struct.
```csharp
public struct NoConstructor { }
public struct PublicConstructor { public PublicConstructor() { } }
public struct InternalConstructor { internal InternalConstructor() { } }
public struct PrivateConstructor { private PrivateConstructor() { } }
```
The same set of modifiers can be used for parameterless constructors as other instance constructors: `extern`, and `unsafe`.
Constructors cannot be `partial`.
### Executing field initializers
Execution of struct instance field initializers matches execution of [class field initializers](https://github.com/dotnet/csharplang/blob/master/spec/classes.md#instance-variable-initializers):
> When an instance constructor has no constructor initializer, ... that constructor implicitly performs the initializations specified by the _variable_initializers_ of the instance fields ... . This corresponds to a sequence of assignments that are executed immediately upon entry to the constructor ... . The variable initializers are executed in the textual order in which they appear in the ... declaration.
### Definite assignment
Instance fields must be definitely assigned in struct instance constructors that do not have a `this()` initializer (see [struct constructors](https://github.com/dotnet/csharplang/blob/master/spec/structs.md#constructors)).
Definite assignment of instance fields is required within explicit parameterless constructors as well.
```csharp
struct S1
{
int x = 1;
object y;
S() { } // error: field 'y' must be assigned
}
struct S2
{
int x = 2;
object y;
S() : this(null) { } // ok
S(object y) { this.y = y; } // ok
}
```
_Should definite assignment of struct instance fields be required within synthesized parameterless constructors?_
_If so, then if any instance fields have initializers, all instance fields must have initializers._
```csharp
struct S0
{
int x = 0;
object y;
// ok?
}
```
If fields are not explicitly initialized, the constructor will need to zero the instance before executing any field initializers.
```
.class S0 extends System.ValueType
{
.field int32 x
.field object y
.method public instance void .ctor()
{
ldarg.0
initobj S0
ldarg.0
ldc.i4.0
stfld int32 S0::x
ret
}
}
```
### No `base()` initializer
A `base()` initializer is disallowed in struct constructors.
The compiler will not emit a call to the base `System.ValueType` constructor from any struct instance constructors including explicit and synthesized parameterless constructors.
### Fields
The synthesized parameterless constructor will zero fields rather than calling any parameterless constructors for the field types.
_Should the compiler report a warning when constructors for fields are ignored?_
```csharp
struct S0
{
public S0() { }
}
struct S1
{
S0 F; // S0::.ctor() ignored
}
struct S<T> where T : struct
{
T F; // constructor ignored
}
```
### `default` expression
`default` ignores the parameterless constructor and generates a zeroed instance.
_Should the compiler report a warning when a constructor is ignored?_
```csharp
_ = default(NoConstructor); // ok
_ = default(PublicConstructor); // ok: constructor ignored
_ = default(PrivateConstructor); // ok: constructor ignored
```
### Object creation
Object creation expressions require the parameterless constructor to be accessible if defined.
The parameterless constructor is invoked explicitly.
_This is a breaking change if the struct type with parameterless constructor is from an existing assembly._
_Should the compiler report a warning rather than an error for `new()` if the constructor is inaccessible, and emit `initobj`, for compatability?_
```csharp
_ = new NoConstructor(); // ok: initobj NoConstructor
_ = new PublicConstructor(); // ok: call PublicConstructor::.ctor()
_ = new PrivateConstructor(); // error: 'PrivateConstructor..ctor()' is inaccessible
```
### Uninitialized values
A local or field of a struct type that is not explicitly initialized is zeroed.
The compiler reports a definite assignment error for an uninitialized struct that is not empty.
```csharp
NoConstructor s1;
PublicConstructor s2;
s1.ToString(); // error: use of unassigned local (unless type is empty)
s2.ToString(); // error: use of unassigned local (unless type is empty)
```
### Array allocation
Array allocation ignores any parameterless constructor and generates zeroed elements.
_Should the compiler warn that the parameterless constructor is ignored? How would such a warning be avoided?_
```csharp
_ = new NoConstructor[1]; // ok
_ = new PublicConstructor[1]; // ok: constructor ignored
_ = new PrivateConstructor[1]; // ok: constructor ignored
```
### Parameter default values
Parameterless constructors cannot be used as parameter default values.
_This is a breaking change if the struct type with parameterless constructor is from an existing assembly._
_Should the compiler report a warning rather than an error for `new()` if the constructor is inaccessible, and emit `default`, for compatability?_
```csharp
static void F1(NoConstructor s1 = new()) { } // ok
static void F2(PublicConstructor s1 = new()) { } // error: default value must be constant
```
### Constraints
The `new()` type parameter constraint requires the parameterless constructor to be `public` if defined (see [satisfying constraints](https://github.com/dotnet/csharplang/blob/master/spec/types.md#satisfying-constraints)).
```csharp
static T CreateNew<T>() where T : new() => new T();
_ = CreateNew<NoConstructor>(); // ok
_ = CreateNew<PublicConstructor>(); // ok
_ = CreateNew<InternalConstructor>(); // error: 'InternalConstructor..ctor()' is not public
```
_Should the compiler report a warning rather than an error when substituting a struct with a non-public constructor for a type parameter with a `new()` constraint, for compatability and to avoid assuming the type is actually instantiated?_
`new T()` is emitted as a call to `System.Activator.CreateInstance<T>()`, and the compiler assumes the implementation of `CreateInstance<T>()` invokes the `public` parameterless constructor if defined.
_With .NET Framework, `Activator.CreateInstance<T>()` invokes the parameterless constructor if the constraint is `where T : new()` but appears to ignore the parameterless constructor if the constraint is `where T : struct`._
There is a gap in type parameter constraint checking because the `new()` constraint is satisfied by a type parameter with a `struct` constraint (see [satisfying constraints](https://github.com/dotnet/csharplang/blob/master/spec/types.md#satisfying-constraints)).
As a result, the following will be allowed by the compiler but `Activator.CreateInstance<InternalConstructor>()` will fail at runtime.
The issue is not introduced by this proposal though - the issue exists with C# 9 if the struct type with inaccessible parameterless constructor is from metadata.
```csharp
static T CreateNew<T>() where T : new() => new T();
static T CreateStruct<T>() where T : struct => CreateNew<T>();
_ = CreateStruct<InternalConstructor>(); // compiles; 'MissingMethodException' at runtime
```
### Optional parameters
Constructors with optional parameters are not considered parameterless constructors. This behavior is unchanged from earlier compiler versions.
```csharp
struct S1 { public S1(string s = "") { } }
struct S2 { public S2(params object[] args) { } }
_ = new S1(); // ok: ignores constructor
_ = new S2(); // ok: ignores constructor
```
### Metadata
Explicit and synthesized parameterless struct instance constructors will be emitted to metadata.
Parameterless struct instance constructors will be imported from metadata regardless of accessibility.
_This might be a breaking change for consumers of existing assemblies with structs with private parameterless constructors if additional errors or warnings are reported._
Parameterless struct instance constructors will be emitted to ref assemblies regardless of accessibility to allow consumers to differentiate between no parameterless constructor an inaccessible constructor.
## See also
- https://github.com/dotnet/roslyn/issues/1029
## Design meetings
- https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-01-27.md#field-initializers

150
proposals/params-span.md Normal file
View file

@ -0,0 +1,150 @@
# `params Span<T>`
## Summary
Avoid heap allocation for implicit allocation of arrays in specific scenarios with `params` arguments.
## Motivation
`params` array parameters provide a convenient way to call a method that takes an arbitrary length list of arguments.
However, using an array type for the parameter means the compiler must implicitly allocate an array on the heap at each call site.
If we extend `params` types to include the `ref struct` types `Span<T>` and `ReadOnlySpan<T>`, where values of those types cannot escape the call stack, the array at the call site may be created on the stack instead.
And if we're extending `params` to other types, we could also allow `params IEnumerable<T>` to avoid allocating and copying collections at call sites that have an `IEnumerable<T>` rather than `T[]`.
The benefits of `params ReadOnlySpan<T>` and `params Span<T>` are primarily for new APIs. Existing commonly used APIs such as `Console.WriteLine()` and `StringBuilder.AppendFormat()` already have overloads that avoid array allocations for common cases and those overloads would need to be retained for backward compatibility.
```csharp
public static class Console
{
public static void WriteLine(string value);
public static void WriteLine(string format, object arg0);
public static void WriteLine(string format, object arg0, object arg1);
public static void WriteLine(string format, object arg0, object arg1, object arg2);
public static void WriteLine(string format, params object[] arg);
}
```
## Detailed design
### Extending `params`
`params` parameters will be supported with types `Span<T>`, `ReadOnlySpan<T>`, and `IEnumerable<T>`.
A call in [_expanded form_](../spec/expressions.md#applicable-function-member) to a method with a `params T[]` or `params IEnumerable<T>` parameter will result in an array `T[]` allocated on the heap.
A call in [_expanded form_](../spec/expressions.md#applicable-function-member) to a method with a `params ReadOnlySpan<T>` or `params Span<T>` parameter will result in an array `T[]` created on the stack _if the `params` array is within limits (if any) set by the compiler_.
Otherwise the array will be allocated on the heap.
```csharp
Console.WriteLine(fmt, x, y, z); // WriteLine(string format, params ReadOnlySpan<object?> arg)
```
The compiler will report an error when compiling the method declaring the `params` parameter if the `ReadOnlySpan<T>` or `Span<T>` parameter value is returned from the method or assigned to an `out` parameter.
That ensures call-sites can create the underlying array on the stack and reuse the array across call-sites without concern for aliases.
A `params` parameter must be last parameter in the method signature.
Two overloads cannot differ by `params` modifier alone.
`params` parameters will be marked in metadata with a `System.ParamArrayAttribute` regardless of type.
### Overload resolution
Overload resolution will continue to prefer overloads that are applicable in [_normal form_](../spec/expressions.md#applicable-function-member) rather than [_expanded form_](../spec/expressions.md#applicable-function-member).
For overloads that are applicable in _expanded form_, [better function member](../spec/expressions.md#better-function-member) will be updated to prefer `params` types in a specific order:
> When performing this evaluation, if `Mp` or `Mq` is applicable in its expanded form, then `Px` or `Qx` refers to a parameter in the expanded form of the parameter list.
>
> In case the parameter type sequences `{P1, P2, ..., Pn}` and `{Q1, Q2, ..., Qn}` are equivalent (i.e. each `Pi` has an identity conversion to the corresponding `Qi`), the following tie-breaking rules are applied, in order, to determine the better function member.
>
> * If `Mp` is a non-generic method and `Mq` is a generic method, then `Mp` is better than `Mq`.
> * ...
> * **Otherwise, if both methods have `params` parameters and are applicable only in their expanded forms, and the `params` types are distinct types with equivalent element type (there is an identity conversion between element types), the more specific `params` type is the first of:**
> * **`ReadOnlySpan<T>`**
> * **`Span<T>`**
> * **`T[]`**
> * **`IEnumerable<T>`**
> * Otherwise if one member is a non-lifted operator and the other is a lifted operator, the non-lifted one is better.
> * Otherwise, neither function member is better.
### Array creation expressions
Array creation expressions that are target-typed to `ReadOnlySpan<T>` or `Span<T>` will be created on the stack _if the length of the array is a constant value within limits (if any) set by the compiler_.
Otherwise the array will be allocated on the heap.
```csharp
Span<int> s = new[] { i, j, k }; // int[] on the stack
WriteLine(fmt, new[] { x, y, z }); // object[] on the stack for WriteLine(string fmt, ReadOnlySpan<object> args);
```
### Array re-use
The compiler _may_ reuse an implicitly allocated array across multiple uses within a single thread executing a method:
- At the same call-site (within a loop) or
- At distinct call-sites if the lifetime of the spans do not overlap, and the array length is sufficient, and
- the element types are managed types that are considered identical by the runtime, or
- the element types are unmanaged types of the same size.
An implicitly allocated array may be reused regardless of whether the array was created on the stack or the heap.
### Lowering implicit allocation
For the `params` and array creation cases above that are target typed to `Span<T>` or `ReadOnlySpan<T>`, the compiler will lower the creation of spans using an efficient approach, specifically avoiding heap allocations when possible.
The exact details are still to be determined and may differ based on the target framework and runtime.
The guarantee the compiler gives is the span will be the expected size and will contain the expected items at any point in user code.
## Open issues
### Is `params Span<T>` necessary?
Is there a reason to support `params` parameters of type `Span<T>` in addition to `ReadOnlySpan<T>`? Is allowing mutation within the `params` method useful?
### Is `params IEnumerable<T>` necessary?
If the compiler allows `params ReadOnlySpan<T>`, then new APIs that require `params` could use `params ReadOnlySpan<T>` instead of `params T[]` because `T[]` is implicitly convertible to `ReadOnlySpan<T>`. And existing APIs could add a `params ReadOnlySpan<T>` overload where the existing `params T[]` simply delegates to the new overload.
There is no conversion from `IEnumerable<T>` to `ReadOnlySpan<T>` however, so allowing `params IEnumerable<T>` is essentially asking APIs to provide two overloads for `params` methods: `params ReadOnlySpan<T>` and `params IEnumerable<T>`.
Are scenarios for `params IEnumerable<T>` sufficiently compelling to justify that?
### Array limits
The compiler may use heuristics to determine when to fallback to heap allocation for the underlying data for spans.
If heuristics are necessary, experimentation should establish the limits we agree on.
### Lowering approach
We need to determine the particular approach used to lower `params` and array creation expressions to avoid heap allocation.
For instance, one potential approach to represent a `Span<T>` of constant length `N` is to synthesize a `struct` with `N` fields of type `T`
where the layout and alignment of the fields matches the alignment of elements in `T[]`, and create the `Span<T>` from a `ref` to the first field of the `struct`.
With that approach, `Console.WriteLine(fmt, x, y, z);` would be emitted as:
```csharp
[StructLayout(LayoutKind.Sequential)]
internal struct __ValueArray3<T> { public T Item1, Item2, Item3; };
var values = new __ValueArray3<object>() { Item1 = x, Item2 = y, Item3 = z };
var span = MemoryMarshal.CreateSpan(ref values.Item1, 3);
Console.WriteLine(fmt, (ReadOnlySpan<object>)span); // WriteLine(string format, params ReadOnlySpan<object?> arg)
```
Alternative approaches may require runtime support.
### Explicit `stackalloc`
Should we allow explicit stack allocation of arrays of managed types with `stackalloc` as well?
```csharp
public static ImmutableArray<TResult> Select<TSource, TResult>(this ImmutableArray<TSource> source, Func<TSource, TResult> map)
{
int n = source.Length;
Span<TResult> result = n <= 16 ? stackalloc TResult[n] : new TResult[n];
for (int i = 0; i < n; i++)
result[i] = map(source[i]);
return ImmutableArray.Create(result); // requires ImmutableArray.Create<T>([DoesNotEscape] ReadOnlySpan<T> items)
}
```
This would require runtime support for stack allocation of arrays of non-constant length and any type, and GC tracking of the elements.
Direct runtime support for stack allocation of arrays of managed types might be useful for lowering implicit allocation as well.
The GC does not currently track the lifetime of a `stackalloc` array so if the contents of the array have a shorter lifetime than the method, the compiler will need to zero the contents of the array so the lifetime of elements matches expectations.
### Opting out
Should we allow opt-ing out of _implicit allocation_ on the call stack?
Perhaps an attribute that can be applied to a method, type, or assembly.
## Related proposals
- https://github.com/dotnet/csharplang/issues/1757
- https://github.com/dotnet/csharplang/blob/main/proposals/format.md#extending-params

View file

@ -1,10 +1,5 @@
# Pattern match `Span<char>` on a constant string
* [x] Proposed
* [x] Prototype: Completed
* [x] Implementation: In Progress
* [ ] Specification: Not Started
## Summary
[summary]: #summary

View file

@ -0,0 +1,73 @@
# Variable declarations under disjunctive patterns
## Summary
Allow variable declarations under `or` patterns and across `case` labels in a `switch` section.
## Motivation
This feature would reduce code duplication where we could use the same piece of code if either of patterns is satisfied. For instance:
```cs
if (e is (int x, 0) or (0, int x))
Use(x);
switch (e)
{
case (int x, 0):
case (0, int x):
Use(x);
break;
}
```
Instead of:
```cs
if (e is (int x1, 0))
Use(x1);
else if (e is (0, int x2))
Use(x2);
switch (e)
{
case (int x, 0):
Use(x);
break;
case (0, int x):
Use(x);
break;
}
```
## Detailed design
Variables *must* be redeclared under all disjuncitve patterns because assignment of such variables depend on the order of evaluation which is undefined in the context of pattern-matching.
- In a *disjunctive_pattern*, pattern variables declared on one side must be redeclared on the other side.
- In a *switch_section*, pattern variables declared under each case label must be redeclared under every other case label.
In any other case, variable declaration follows the usual scoping rules and is disallowed.
These names can reference either of variables based on the result of the pattern-matching at runtime. Under the hood, it's the same local being assigned in each pattern.
Redeclaring pattern variables is only permitted for variables of the same type.
## Unresolved questions
- How identical these types should be?
- Could we support variable declarations under `not` patterns?
```cs
if (e is not (int x, 0) and not (0, int x))
```
- Could we relax the scoping rules beyond pattern boundaries?
```cs
if (e is (int x, 0) || a is (0, int x))
```
- Could we relax the redeclaration requirement in a switch section?
```cs
case (int x, 0) a when Use(x, a): // ok
case (0, int x) b when Use(x, b): // ok
Use(x); // ok
Use(a); // error; not definitely assigned
Use(b); // error; not definitely assigned
break;
```

View file

@ -0,0 +1,554 @@
# Raw string literal
## Summary
Allow a new form of string literal that starts with a minimum of three `"""` characters (but no maximum), optionally followed by a `new_line`, the content of the string, and then ends with the same number of quotes that the literal started with. For example:
```
var xml = """
<element attr="content"/>
""";
```
Because the nested contents might itself want to use `"""` then the starting/ending delimiters can be longer like so:
```
var xml = """"
Ok to use """ here
"""";
```
To make the text easy to read and allow for indentation that developers like in code, these string literals will naturally remove the indentation specified on the last line when producing the final literal value. For example, a literal of the form:
```
var xml = """
<element attr="content">
<body>
</body>
</element>
""";
```
Will have the contents:
```
<element attr="content">
<body>
</body>
</element>
```
This allows code to look natural, while still producing literals that are desired, and avoiding runtime costs if this required the use of specialized string manipulation routines.
If the indentation behavior is not desired, it is also trivial to disable like so:
```
var xml = """
<element attr="content">
<body>
</body>
</element>
""";
```
A single line form is also supported. It starts with a minimum of three `"""` characters (but no maximum), the content of the string (which cannot contain any `new_line` characters), and then ends with the same number of quotes that the literal started with. For example:
```
var xml = """<summary><element attr="content"/></summary>""";
```
## Motivation
C# lacks a general way to create simple string literals that can contain effectively any arbitrary text. All C# string literal forms today need some form of escaping in case the contents use some special character (always if a delimiter is used). This prevents easily having literals containing other languages in them (for example, an XML, HTML or JSON literal).
All current approaches to form these literals in C# today always force the user to manually escape the contents. Editing at that point can be highly annoying as the escaping cannot be avoided and must be dealt with whenever it arises in the contents. This is particularly painful for regexes, especially when they contain quotes or backslashes. Even with a `@""` string, quotes themselves must be escaped leading to a mix of C# and regex interspersed. `{` and `}` are similarly frustrating in `$""` strings.
The crux of the problem is that all our strings have a fixed start/end delimiter. As long as that is the case, we will always have to have an escaping mechanism as the string contents may need to specify that end delimiter in their contents. This is particularly problematic as that delimiter `"` is exceedingly common in many languages.
To address this, this proposal allows for flexible start and end delimiters so that they can always be made in a way that will not conflict with the content of the string.
## Goals
1. Provide a mechanism that will allow *all* string values to be provided by the user without the need for *any* escape-sequences whatsoever. Because all strings must be representable without escape-sequences, it must always be possible for the user to specify delimiters that will be guaranteed to not collide with any text contents.
2. Support interpolations in the same fashion. As above, because *all* strings must be representable without escapes, it must always be possible for the user to specify an `interpolation` delimiter that will be guaranteed to not collide with any text contents. Importantly, languages that use our `interpolation` delimiter characters (`{` and `}`) should feel first-class and not painful to use.
3. Multiline string literals should look pleasant in code and should not make indentation within the compilation unit look strange. Importantly, literal values that themselves have no indentation should not be forced to occupy the first column of the file as that can break up the flow of code and will look unaligned with the rest of the code that surrounds it.
* This behavior should be easy to override while keeping literals clear and easy to read.
4. For all strings that do not themselves contain a `new_line` or start or end with a quote (`"`) character, it should be possible to represent the string literal itself on a single line.
- Optionally, with extra complexity, we could refine this to state that: For all strings that do not themselves contain a `new_line` (but can start or end with a quote `"` character), it should be possible to represent the string literal itself on a single line. For more details see the expanded proposal in the `Drawbacks` section.
## Detailed design (non-interpolation case)
We will add a new `string_literal` production with the following form:
```
string_literal
: regular_string_literal
| verbatim_string_literal
| raw_string_literal
;
raw_string_literal
: single_line_raw_string_literal
| multi_line_raw_string_literal
;
raw_string_literal_delimiter
: """
| """"
| """""
| etc.
;
raw_content
: not_new_line+
;
single_line_raw_string_literal
: raw_string_literal_delimiter raw_content raw_string_literal_delimiter
;
multi_line_raw_string_literal
: raw_string_literal_delimiter whitespace* new_line (raw_content | new_line)* new_line whitespace* raw_string_literal_delimiter
;
not_new_line
: <any unicode character that is not new_line>
;
```
The ending delimiter to a `raw_string_literal` must match the starting delimiter. So if the starting delimiter is `"""""` the ending delimiter must be that as well.
The above grammar for a `raw_string_literal` should be interpreted as:
1. It starts with at least three quotes (but no upper bound on quotes).
2. It then continues with contents on the same line as the starting quotes. These contents on the same line can be blank, or non-blank. 'blank' is synonymous with 'entirely whitespace'.
3. If the contents on that same line is non-blank no further content can follow. In other words the literal is required to end with the same number of quotes on that same line.
4. If the contents on the same line is blank, then the literal can continue with a `new_line` and number of subsequent content lines and `new_line`s.
- A content line is any text except a `new_line`.
- It then ends with a `new_line` some number (possibly zero) of `whitespace` and the same number of quotes that the literal started with.
## Raw string literal value
The portions between the starting and ending `raw_string_literal_delimiter` are used to form the value of the `raw_string_literal` in the following fashion:
* In the case of `single_line_raw_string_literal` the value of the literal will exactly be the contents between the starting and ending `raw_string_literal_delimiter`.
* In the case of `multi_line_raw_string_literal` the initial `whitespace* new_line` and the final `new_line whitespace*` is not part of the value of the string. However, the final `whitespace*` portion preceding the `raw_string_literal_delimiter` terminal is considered the 'indentation whitespace' and will affect how the other lines are interpreted.
* To get the final value the sequence of `(raw_content | new_line)*` is walked and the following is performed:
* If it a `new_line` the content of the `new_line` is added to the final string value.
* If it is not a 'blank' `raw_content` (i.e. `not_new_line+` contains a non-`whitespace` character):
* the 'indentation whitespace' must be a prefix of the `raw_content`. It is an error otherwise.
* the 'indentation whitespace' is stripped from the start of `raw_content` and the remainder is added to the final string value.
* If it is a 'blank' `raw_content` (i.e. `not_new_line+` is entirely `whitespace`):
* the 'indentation whitespace' must be a prefix of the `raw_content` or the `raw_content` must be a prefix of of the 'indentation whitespace'. It is an error otherwise.
* as much of the 'indentation whitespace' is stripped from the start of `raw_content` and any remainder is added to the final string value.
## Clarifications:
1. A `single_line_raw_string_literal` is not capable of representing a string with a `new_line` value in it. A `single_line_raw_string_literal` does not participate in the 'indentation whitespace' trimming. Its value is always the exact characters between the starting and ending delimiters.
2. Because a `multi_line_raw_string_literal` ignores the final `new_line` of the last content line, the following represents a string with no starting `new_line` and no terminating `new_line`
```
var v1 = """
This is the entire content of the string.
"""
```
This maintains symmetry with how the starting `new_line` is ignored, and it also provides a uniform way to ensure the 'indentation whitespace' can always be adjusted. To represent a string with a terminal `new_line` an extra line must be provided like so:
```
var v1 = """
This string ends with a new line.
"""
```
3. A `single_line_raw_string_literal` cannot represent a string value that starts or ends with a quote (`"`) though an augmentation to this proposal is provided in the `Drawbacks` section that shows how that could be supported.
4. A `multi_line_raw_string_literal` starts with `whitespace* new_line` following the initial `raw_string_literal_delimiter`. This content after the delimiter is entirely ignored and is not used in any way when determining the value of the string. This allows for a mechanism to specify a `raw_string_literal` whose content starts with a `"` character itself. For example:
```
var v1 = """
"The content of this string starts with a quote
"""
```
5. A `raw_string_literal` can also represent content that end with a quote (`"`). This is supported as the terminating delimiter must be on its own line. For example:
```
var v1 = """
"The content of this string starts and ends with a quote"
"""
```
```
var v1 = """
""The content of this string starts and ends with two quotes""
"""
```
5. The requirement that a 'blank' `raw_content` be either a prefix of the 'indentation whitespace' or the 'indentation whitespace' must be a prefix of it helps ensure confusing scenarios with mixed whitespace do not occur, especially as it would be unclear what should happen with that line. For example, the following case is illegal:
```
var v1 = """
Start
<tab>
End
"""
```
6. Here the 'indentation whitespace' is nine space characters, but the 'blank' `raw_content` does not start with a prefix of that. There is no clear answer as to how that `<tab>` line should be treated at all. Should it be ignored? Should it be the same as `.........<tab>`? As such, making it illegal seems the clearest for avoiding confusion.
7. The following cases are legal though and represent the same string:
```
var v1 = """
Start
<four spaces>
End
"""
```
```
var v1 = """
Start
<nine spaces>
End
"""
```
In both these cases, the 'indentation whitespace' will be nine spaces. And in both cases, we will remove as much of that prefix as possible, leading the 'blank' `raw_content` in each case to be empty (not counting every `new_line`). This allows users to not have to see and potentially fret about whitespace on these lines when they copy/paste or edit these lines.
8. In the case though of:
```
var v1 = """
Start
<ten spaces>
End
"""
```
The 'indentation whitespace' will still be nine spaces. Here though, we will remove as much of the 'indentation whitespace' as possible, and the 'blank' `raw_content` will contribute a single space to the final content. This allows for cases where the content does need whitespace on these lines that should be preserved.
9. The following is technically not legal:
```
var v1 = """
"""
```
This is because the start of the raw string must have a `new_line` (which is does) but the end must have a `new_line` as well (which it does not). The minimal legal `raw_string_literal` is:
```
var v1 = """
"""
```
However, this string is decidedly uninteresting as it is equivalent to `""`.
## Indentation examples
The 'indentation whitespace' algorithm can be visualized on several inputs like so:
### Example 1 - Standard case
```
var xml = """
<element attr="content">
<body>
</body>
</element>
""";
```
is interpreted as
```
var xml = """
|<element attr="content">
| <body>
| </body>
|</element>
""";
```
### Example 2 - End delimiter on same line as content.
```
var xml = """
<element attr="content">
<body>
</body>
</element>""";
```
This is illegal. The last content line must end with a `new_line`.
### Example 3 - End delimiter before start delimiter
```
var xml = """
<element attr="content">
<body>
</body>
</element>
""";
```
is interpreted as
```
var xml = """
| <element attr="content">
| <body>
| </body>
| </element>
""";
```
### Example 4 - End delimiter after start delimiter
```
var xml = """
<element attr="content">
<body>
</body>
</element>
""";
```
This is illegal. The lines of content must start with the 'indentation whitespace'
### Example 5 - Empty blank line
```
var xml = """
<element attr="content">
<body>
</body>
</element>
""";
```
is interpreted as
```
var xml = """
|<element attr="content">
| <body>
| </body>
|
|</element>
""";
```
### Example 5 - Blank line with less whitespace than prefix (dots represent spaces)
```
var xml = """
<element attr="content">
<body>
</body>
....
</element>
""";
```
is interpreted as
```
var xml = """
|<element attr="content">
| <body>
| </body>
|
|</element>
""";
```
### Example 5 - Blank line with more whitespace than prefix (dots represent spaces)
```
var xml = """
<element attr="content">
<body>
</body>
..............
</element>
""";
```
is interpreted as
```
var xml = """
|<element attr="content">
| <body>
| </body>
|....
|</element>
""";
```
## Detailed design (interpolation case)
Interpolations in normal interpolated strings (e.g. `$"..."`) are supported today through the use of the `{` character to start an `interpolation` and the use of an `{{` escape-sequence to insert an actual open brace character. Using this same mechanism would violate goals '1' and '2' of this proposal. Languages that have `{` as a core character (examples being JavaScript, JSON, Regex, and even embedded C#) would now need escaping, undoing the purpose of raw string literals.
To support interpolations we introduce them in a different fashion than normal `$"` interpolated strings. Specifically, an `interpolated_raw_string_literal` will start with some number of `$` characters. The count of these indicates how many `{` (and `}`) characters are needed in the content of the literal to delimit the `interpolation`. Importantly, there continues to be no escaping mechanism for curly braces. Rather, just as with quotes (`"`) the literal itself can always ensure it specifies delimiters for the interpolations that are certain to not collide with any of the rest of the content of the string. For example a JSON literal containing interpolation holes can be written like so:
```
var v1 = $$"""
{
"orders":
[
{ "number": {{order_number}} }
]
}
"""
```
Here, the `{{...}}` matches the requisite count of two braces specified by the `$$` delimiter prefix. In the case of a single `$` that means the interpolation is specified just as `{...}` as in normal interpolated string literals.
Interpolated raw string literals are defined as:
```
interpolated_raw_string_literal
: single_line_interpolated_raw_string_literal
| multi_line_interpolated_raw_string_literal
;
interpolated_raw_string_start
: $
| $$
| $$$
| etc.
;
interpolated_raw_string_literal_delimiter
: interpolated_raw_string_start raw_string_literal_delimiter
;
single_line_interpolated_raw_string_literal
: interpolated_raw_string_literal_delimiter interpolated_raw_content raw_string_literal_delimiter
;
multi_line_interpolated_raw_string_literal
: interpolated_raw_string_literal_delimiter whitespace* new_line (interpolated_raw_content | new_line)* new_line whitespace* raw_string_literal_delimiter
;
interpolated_raw_content
: (not_new_line | raw_interpolation)+
;
raw_interpolation
: raw_interpolation_start interpolation raw_interpolation_end
;
raw_interpolation_start
: {
| {{
| {{{
| etc.
;
raw_interpolation_end
: }
| }}
| }}}
| etc.
;
```
The above is similar to the definition of `raw_string_literal` but with some important differences. A `interpolated_raw_string_literal` should be interpreted as:
1. It starts with at least one dollar sign (but no upper bound) and then three quotes (also with no upper bound).
2. It then continues with content on the same line as the starting quotes. This content on the same line can be blank, or non-blank. 'blank' is synonymous with 'entirely whitespace'.
3. If the content on that same line is non-blank no further content can follow. In other words the literal is required to end with the same number of quotes on that same line.
4. If the contents on the same line is blank, then the literal can continue with a `new_line` and number of subsequent content lines and `new_line`s.
- A content line is any text except a `new_line`.
- A content line can contain multiple `raw_interpolation` occurrences at any position. The `raw_interpolation` must start with an equal number of open braces (`{`) as the number of dollar signs at the start of the literal.
- The `raw_interpolation` will following the normal rules specified at https://github.com/dotnet/csharplang/blob/main/spec/expressions.md#interpolated-strings. Any `raw_interpolation` must end with the same number of close braces (`}`) as dollar signs and open braces.
- Any `interpolation` can itself contain new-lines within in the same manner as an `interpolation` in a normal `verbatim_string_literal` (`@""`).
- It then ends with a `new_line` some number (possibly zero) of `whitespace` and the same number of quotes that the literal started with.
Computation of the interpolated string value follows the same rules as a normal `raw_string_literal` except updated to handle lines containing `raw_interpolation`s. Building the string value happens in the same fashion, just with the interpolation holes replaced with whatever values those expressions produce at runtime. If the `interpolated_raw_string_literal` is converted to a `FormattableString` then the values of the interpolations are passed in their respective order to the `arguments` array to `FormattableString.Create`. The rest of the content of the `interpolated_raw_string_literal` *after* the 'indentation whitespace' has been stripped from all lines will be used to generate `format` string passed to `FormattableString.Create`, except with appropriately numbered `{N}` contents in each location where a `raw_interpolation` occurred (or `{N,constant}` in the case if its `interpolation` is of the form `expression ',' constant_expression`).
There is an ambiguity in the above specification. Specifically when a section of `{` in text and `{` of an interpolation abut. For example:
```
var v1 = $$"""
{{{order_number}}}
"""
```
This could be interpreted as: `{{ {order_number } }}` or `{ {{order_number}} }`. However, as the former is illegal (no C# expression could start with `{`) it would be pointless to interpret that way. So we interpret in the latter fashion, where the innermost `{` and `}` braces form the interpolation, and any outermost ones form the text. In the future this might be an issue if the language ever supports any expressions that are surrounded by braces. However, in that case, the recommendation would be to write such a case like so: `{{({some_new_expression_form})}}`. Here, parentheses would help designate the expression portion from the rest of the literal/interpolation. This has precedence already with how ternary conditional expressions need to be wrapped to not conflict with the formatting/alignment specifier of an interpolation (e.g. `{(x ? y : z)}`).
Examples: (upcoming)
## Drawbacks
Raw string literals add more complexity to the language. We already have many string literal forms already for numerous purposes. `""` strings, `@""` strings, and `$""` strings already have a lot of power and flexibility. But they all lack a way to provide raw contents that never need escaping.
The above rules do not support the case of 4.a:
4. ...
- Optionally, with extra complexity, we could refine this to state that: For all strings that do not themselves contain a `new_line` (but can start or end with a quote `"` character), it should be possible to represent the string literal itself on a single line.
That's because we have no means to know that a starting or ending quote (`"`) should belong to the contents and not the delimiter itself. If this is an important scenario we want to support though, we can add a parallel `'''` construct to go along with the `"""` form. With that parallel construct, a single line string that start and ends with `"` can be written easily as `'''"This string starts and ends with quotes"'''` along with the parallel construct `"""'This string starts and ends with apostrophes'"""`. This may also be desirable to support to help visually separate out quote characters, which may help when embedding languages that primarily use one quote character much more than then other.
## Alternatives
https://github.com/dotnet/csharplang/discussions/89 covers many options here. Alternatives are numerous, but i feel stray too far into complexity and poor ergonomics. This approach opts for simplicity where you just keep increasing the start/end quote length until there is no concern about a conflict with the string contents. It also allows the code you write to look well indented, while still producing a dedented literal that is what most code wants.
One of the most interesting potential variations though is the use of `` ` `` (or ` ``` `) fences for these raw string literals. This would have several benefits:
1. It would avoid all the issues with strings starting er ending with quotes.
2. It would look familiar to markdown. Though that in itself is potentially not a good thing as users might expect markdown interpretation.
3. A raw string literal would only have to start and end with a single character in most cases, and would only need multiple in the much rarer case of contents that contain back-ticks themselves.
4. It would feel natural to extend this in the future with ` ```xml `, again akin to markdown. Though, of course, that is also true of the `"""` form.
Overall though, the net benefit here seems small. In keeping with C# history, i think `"` should continue to be the `string literal` delimiter, just as it is for `@""` and `$""`.
## Design meetings
### ~~Open issues to discuss~~ Resolved issues:
- [x] should we have a single line form? We technically could do without it. But it would mean simple strings not containing a newline would always take at least three lines. I think we should It's very heavyweight to force single line constructs to be three lines just to avoid escaping.
Design decision: Yes, we will have a single line form.
- [x] should we require that multiline *must* start with a newline? I think we should. It also gives us the ability to support things like `"""xml` in the future.
Design decision: Yes, we will require that multiline must start with a newline
- [x] should the automatic dedenting be done at all? I think we should. It makes code look so much more pleasant.
Design decision: Yes, automatic dedenting will be done.
- [x] should we restrict common-whitespace from mixing whitespace types? I don't think we should. Indeed, there is a common indentation strategy called "tab for indentation, space for alignment". It would be very natural to use this to align the end delimiter with the start delimiter in a case where the start delimiter doesn't start on a tab stop.
Design decision: We will not have any restrictions on mixing whitespace.
- [x] should we use something else for the fences? `` ` `` would match markdown syntax, and would mean we didn't need to always start these strings with three quotes. Just one would suffice for the common case.
Design decision: We will use `"""`
- [x] should we have a requirement that the delimiter have more quotes than the longest sequence of quotes in the string value? Technically it's not required. for example:
```
var v = """
contents"""""
"""
```
This is a string with `"""` as the delimiter. Several community members have stated this is confusing and we should require in a case like this that the delimiter always have more characters. That would then be:
```
var v = """"""
contents"""""
""""""
```
Design decision: Yes, the delimiter must be longer than any sequence of quotes in the string itself.

View file

@ -75,7 +75,7 @@ the type author, with various customizations to allow flexibility for multiple c
`class`, `struct`, and `record` types gain the ability to declare a _required\_member\_list_. This list is the list of all the properties and fields of a type that are considered
_required_, and must be initialized during the construction and initialization of an instance of the type. Types inherit these lists from their base types automatically, providing
a seemless experience that removes boilerplate and repetitive code.
a seamless experience that removes boilerplate and repetitive code.
### `required` modifier

View file

@ -0,0 +1,113 @@
# Semi-auto-properties (a.k.a. `field` keyword in properties)
## Summary
Extend auto-properties to allow them to still have an automatically generated backing field, while still allowing for bodies to be provided for accessors. Auto-properties can also use a new contextual `field` keyword in their body to refer to the auto-prop field.
## Motivation
Standard auto-properties only allow for setting or getting the backing field directly, giving some control only by access modifying the accessor methods. Sometimes there is more need to have control over what happens when accessing an auto-property, without being confronted with all overhead of a standard property.
Two common scenarios are that you want to apply a constraint on the setter, ensuring the validity of a value. The other being raising an event that informs about the property going to be changed/having been changed.
In these cases by now you always have to create an instance field and write the whole property yourself. This not only adds a fair amount of code, but it also leaks the `field` into the rest of the type's scope, when it is often desirable to only have it be available to the bodies of the accessors.
## Specification changes
The following changes are to be made to [classes.md](https://github.com/dotnet/csharplang/blob/main/spec/classes.md):
```
### Automatically implemented properties
```
```diff
- An automatically implemented property (or ***auto-property*** for short), is a non-abstract non-extern
- property with semicolon-only accessor bodies. Auto-properties must have a get accessor and can optionally
- have a set accessor.
+ An automatically implemented property (or ***auto-property*** for short), is a non-abstract non-extern
+ property with either or both of:
```
```diff
- When a property is specified as an automatically implemented property, a hidden backing field is automatically
- available for the property, and the accessors are implemented to read from and write to that backing field. If
- the auto-property has no set accessor, the backing field is considered `readonly` ([Readonly fields](classes.md#readonly-fields)).
- Just like a `readonly` field, a getter-only auto-property can also be assigned to in the body of a constructor
- of the enclosing class. Such an assignment assigns directly to the readonly backing field of the property.
+ 1. an accessor with a semicolon-only body
+ 2. usage of the `field` contextual keyword ([Keywords](lexical-structure.md#keywords)) within the accessors or
+ expression body of the property. The `field` identifier is only considered the `field` keyword when there is
+ no existing symbol named `field` in scope at that location.
+
+ When a property is specified as an auto-property, a hidden, unnamed, backing field is automatically available for
+ the property. For auto-properties, any semicolon-only `get` accessor is implemented to read from, and any semicolon-only
+ `set` accessor to write to its backing field. The backing field can be referenced directly using the `field` keyword
+ within all accessors and within the property expression body. Because the field is unnamed, it cannot be used in a
+ `nameof` expression.
+
+ If the auto-property does not have a set accessor, the backing field can still be assigned to in the body of a
+ constructor of the enclosing class. Such an assignment assigns directly to the backing field of the property.
+
+ If the auto-property has only a semicolon-only get accessor, the backing field is considered `readonly` ([Readonly fields](classes.md#readonly-fields)).
+
+ An auto-property is not allowed to only have a single semicolon-only `set` accessor without a `get` accessor.
```
...
````diff
+The following example:
+```csharp
+// No 'field' symbol in scope.
+public class Point
+{
+ public int X { get; set; }
+ public int Y { get; set; }
+}
+```
+is equivalent to the following declaration:
+```csharp
+// No 'field' symbol in scope.
+public class Point
+{
+ public int X { get { return field; } set { field = value; } }
+ public int Y { get { return field; } set { field = value; } }
+}
+```
+which is equivalent to:
+```csharp
+// No 'field' symbol in scope.
+public class Point
+{
+ private int __x;
+ private int __y;
+ public int X { get { return __x; } set { __x = value; } }
+ public int Y { get { return __y; } set { __y = value; } }
+}
+```
+The following example:
+```csharp
+// No 'field' symbol in scope.
+public class LazyInit
+{
+ public string Value => field ??= ComputeValue();
+ private static string ComputeValue() { /*...*/ }
+}
+```
+is equivalent to the following declaration:
+```csharp
+// No 'field' symbol in scope.
+public class Point
+{
+ private string __value;
+ public string Value { get { return __value ??= ComputeValue(); } }
+ private static string ComputeValue() { /*...*/ }
+}
+```
````
Open LDM questions:
1. If a type does have an existing accessible `field` symbol in scope (like a field called `field`) should there be any way for an auto-prop to still use `field` internally to both create and refer to an auto-prop field. Under the current rules there is no way to do that. This is certainly unfortunate for those users, however this is ideally not a significant enough issue to warrant extra dispensation. The user, after all, can always still write out their properties like they do today, they just lose out from the convenience here in that small case.
## LDM history:
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-10.md#field-keyword
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-14.md#field-keyword

View file

@ -178,6 +178,46 @@ At runtime, the actual member implementation used is the one that exists on the
C c = M<C>(); // The static members of C get called
```
## Variance safety
https://github.com/dotnet/csharplang/blob/main/spec/interfaces.md#variance-safety
Variance safety rules should apply to signatures of static abstract members. The addition proposed in
https://github.com/dotnet/csharplang/blob/main/proposals/variance-safety-for-static-interface-members.md#variance-safety
should be adjusted from
*These restrictions do not apply to occurrences of types within declarations of static members.*
to
*These restrictions do not apply to occurrences of types within declarations of **non-virtual, non-abstract** static members.*
## Processing of user-defined implicit conversions
https://github.com/dotnet/csharplang/blob/main/spec/conversions.md#processing-of-user-defined-implicit-conversions
The following bullet points
* Find the set of types, `D`, from which user-defined conversion operators will be considered. This set consists of `S0` (if `S0` is a class or struct), the base classes of `S0` (if `S0` is a class), and `T0` (if `T0` is a class or struct).
* Find the set of applicable user-defined and lifted conversion operators, `U`. This set consists of the user-defined and lifted implicit conversion operators declared by the classes or structs in `D` that convert from a type encompassing `S` to a type encompassed by `T`. If `U` is empty, the conversion is undefined and a compile-time error occurs.
are adjusted as follows (additions/removals are in bold):
* Find the set of types, `D`, from which user-defined conversion operators will be considered. This set consists of `S0` (if `S0` is a class or struct), the base classes of `S0` (if `S0` is a class), and `T0` (if `T0` is a class or struct). **If `S0` is a type parameter with *effective base class* System.Object, System.ValueType, System.Array or System.Enum, interfaces from its *effective interface set* and their base interfaces are added to the set. If `T0` is a type parameter with *effective base class* System.Object, System.ValueType, System.Array or System.Enum, interfaces from its *effective interface set* and their base interfaces are added to the set.**
* Find the set of applicable user-defined and lifted conversion operators, `U`. This set consists of the user-defined and lifted implicit conversion operators declared by the **~~classes or structs~~types** in `D` that convert from a type encompassing `S` to a type encompassed by `T`. If `U` is empty, the conversion is undefined and a compile-time error occurs.
## Processing of user-defined explicit conversions
https://github.com/dotnet/csharplang/blob/main/spec/conversions.md#processing-of-user-defined-explicit-conversions
The following bullet points
* Find the set of types, `D`, from which user-defined conversion operators will be considered. This set consists of `S0` (if `S0` is a class or struct), the base classes of `S0` (if `S0` is a class), `T0` (if `T0` is a class or struct), and the base classes of `T0` (if `T0` is a class).
* Find the set of applicable user-defined and lifted conversion operators, `U`. This set consists of the user-defined and lifted implicit or explicit conversion operators declared by the classes or structs in `D` that convert from a type encompassing or encompassed by `S` to a type encompassing or encompassed by `T`. If `U` is empty, the conversion is undefined and a compile-time error occurs.
are adjusted as follows (additions/removals are in bold):
* Find the set of types, `D`, from which user-defined conversion operators will be considered. This set consists of `S0` (if `S0` is a class or struct), the base classes of `S0` (if `S0` is a class), `T0` (if `T0` is a class or struct), and the base classes of `T0` (if `T0` is a class). **If `S0` is a type parameter with *effective base class* System.Object, System.ValueType, System.Array or System.Enum, interfaces from its *effective interface set* and their base interfaces are added to the set. If `T0` is a type parameter with *effective base class* System.Object, System.ValueType, System.Array or System.Enum, interfaces from its *effective interface set* and their base interfaces are added to the set.**
* Find the set of applicable user-defined and lifted conversion operators, `U`. This set consists of the user-defined and lifted implicit or explicit conversion operators declared by the **~~classes or structs~~types** in `D` that convert from a type encompassing or encompassed by `S` to a type encompassing or encompassed by `T`. If `U` is empty, the conversion is undefined and a compile-time error occurs.
# Drawbacks
[drawbacks]: #drawbacks
@ -213,9 +253,15 @@ Another *additional* feature would be to allow static members to be abstract and
Called out above, but here's a list:
- Operators `==` and `!=` as well as the implicit and explicit conversion operators are disallowed in interfaces today. Should they be disallowed as static abstract members as well?
- Operators `==` and `!=` as well as the implicit and explicit conversion operators are disallowed in interfaces today. Should they be disallowed as static abstract members as well? Note, the current implementation is adjusted to allow them only in abstract form. If we don't want this behavior after all, there is work to disallow it.
- Should the qualifying `I.` in an explicit operator implementation go before the `operator` keyword or the operator symbol (e.g. `+`) itself?
- Should we relax the operator restrictions further so that the restricted operand can be of any type that derives from, or has one of some set of implicit conversions to the enclosing type?
- The "Operator restrictions" section must provide more precise rules for: "These requirements need to be relaxed so that a restricted operand is allowed to be of a type parameter that is constrained to `T`." What type parameters are allowed, what exactly does it mean to be constraint to `T`, etc. The current implementation allows only type parameters that belong to the immediate contatining type and only those that have containing type as one of the directly specified type constraints (https://github.com/dotnet/roslyn/issues/53801).
Not called out above:
- Confirm whether we would like to support use of static abstract methods declared in interfaces as operators in query expressions (https://github.com/dotnet/roslyn/issues/53796).
- Confirm the rules outlined in "Processing of user-defined implicit conversions" and "Processing of user-defined explicit conversions" sections above. Some feedback on the current rules https://github.com/dotnet/roslyn/issues/56753.
# Design meetings

View file

@ -0,0 +1,57 @@
# Allow using alias directive to reference any kind of Type
## Summary
Relax the [using_alias_directive](https://github.com/dotnet/csharplang/blob/main/spec/namespaces.md#using-alias-directives) to allow it to point at any sort of type, not just named types. This would support types not allowed today, like: tuple types, pointer types, array types, etc. For example, this would now be allowed:
```c#
using Point = (int x, int y);
```
## Motivation
For ages, C# has had the ability to introduce aliases for namespaces and named types (classes, delegated, interfaces, records and structs). This worked acceptably well as it provided a means to introduce non-conflicting names in cases where a normal named pulled in from `using_directive`s might be ambiguous, and it allowed a way to provide a simpler name when dealing with complex generic types. However, the rise of additional complex type symbols in the language has caused more use to arise where aliases would be valuable but are currently not allowed. For example, both tuples and function-pointers often can have large and complex regular textual forms that can be painful to continually write out, and a burden to try to read. Aliases would help in these cases by giving a short, developer-provided, name that can then be used in place of those full structural forms.
## Detailed design
We will change the grammar of `using_alias_directive` thusly:
```
using_alias_directive
- : 'using' identifier '=' namespace_or_type_name ';'
+ : 'using' identifier '=' (namespace_name | type) ';'
;
```
Interestingly, most of the spec language in [using_alias_directive](https://github.com/dotnet/csharplang/blob/main/spec/namespaces.md#using-alias-directives) does not need to change. Most language in it already refers to 'namespace or type', for example:
> A using_alias_directive introduces an identifier that serves as an alias for a namespace or type within the immediately enclosing compilation unit or namespace body.
This remains true, just that the grammar now allows the 'type' to be any arbitrary type, not the limited set allowed for by `namespace_or_type_name` previously.
The sections that do need updating are:
```diff
- The order in which using_alias_directives are written has no significance, and resolution of the namespace_or_type_name referenced by a using_alias_directive is not affected by the using_alias_directive itself or by other using_directives in the immediately containing compilation unit or namespace body. In other words, the namespace_or_type_name of a using_alias_directive is resolved as if the immediately containing compilation unit or namespace body had no using_directives. A using_alias_directive may however be affected by extern_alias_directives in the immediately containing compilation unit or namespace body. In the example
+ The order in which using_alias_directives are written has no significance, and resolution of the `(namespace_name | type)` referenced by a using_alias_directive is not affected by the using_alias_directive itself or by other using_directives in the immediately containing compilation unit or namespace body. In other words, the `(namespace_name | type)` of a using_alias_directive is resolved as if the immediately containing compilation unit or namespace body had no using_directives. A using_alias_directive may however be affected by extern_alias_directives in the immediately containing compilation unit or namespace body. In the example
```
```diff
- The namespace_name referenced by a using_namespace_directive is resolved in the same way as the namespace_or_type_name referenced by a using_alias_directive. Thus, using_namespace_directives in the same compilation unit or namespace body do not affect each other and can be written in any order.
+ The namespace_name referenced by a using_namespace_directive is resolved in the same way as the namespace_or_type_name referenced by a using_alias_directive. Thus, using_namespace_directives in the same compilation unit or namespace body do not affect each other and can be written in any order.
```
## Design meeting open questions.
This section needs to be resolved in a design meeting.
The intent of this specification is to allow one to write something like:
```
using MyPointer = My*;
```
The spec is currently unclear if this would be ok or not. Technically, the `using_alias_directive` here is not in an `unsafe` context, so the `My*` could be considered an error. However, the spirit of this specification is that should be allowed, and only the *usages* of `MyPointer` would themselves have to either be in another `using_alias_directive` or in an `unsafe` context. Another way this could be formalized is that the `(namespace_name | type)` portion of a `using_alias_directive` would always be an `unsafe` context, but that wouldn't negate the fact that any place that alias was referenced would also need to be an `unsafe` context.
--
Similarly what should be done about `using NullablePerson = Person?; // Person is a reference type`? My intuition is that this is fine (though should only be legal if the *using* is in a `#nullable enable` section). The meaning of `NullablePerson` in all reference locations is `Person?` (even if that location is `#nullable disable`). However, depending on the nullability region where it is referenced you may or may not get nullable warnings around it.

View file

@ -0,0 +1,176 @@
Utf8 Strings Literals
===
## Summary
This proposal adds the ability to write UTF8 string literals in C# and have them automatically encoded into their `byte[]` representation.
## Motivation
UTF8 is the language of the web and its use is necessary in significant portions of the .NET stack. While much of data comes in the form of `byte[]` off the network stack there is still significant uses of constants in the code. For example networking stack has to commonly write constants like `"HTTP/1.0\r\n"`, `" AUTH"` or . `"Content-Length: "`.
Today there is no efficient syntax for doing this as C# represents all strings using UTF16 encoding. That means developers have to choose between the convenience of encoding at runtime which incurs overhead, including the time spent at startup actually performing the encoding operation (and allocations if targeting a type that doesn't actually require them), or manually translating the bytes and storing in a `byte[]`.
```c#
// Efficient but verbose and error prone
static ReadOnlySpan<byte> AuthWithTrailingSpace => new byte[] { 0x41, 0x55, 0x54, 0x48, 0x20 };
WriteBytes(AuthWithTrailingSpace);
// Incurs allocation and startup costs performing an encoding that could have been done at compile-time
static readonly byte[] s_authWithTrailingSpace = Encoding.UTF8.GetBytes("AUTH ");
WriteBytes(s_authWithTrailingSpace);
// Simplest / most convenient but terribly inefficient
WriteBytes(Encoding.UTF8.GetBytes("AUTH "));
```
This trade off is a pain point that comes up frequently for our partners in the runtime, ASP.NET and Azure. Often times it causes them to leave performance on the table because they don't want to go through the hassle of writing out the `byte[]` encoding by hand.
To fix this we will allow for UTF8 literals in the language and encode them into the UTF8 `byte[]` at compile time.
## Detailed design
The language will allow conversions between `string` constants and `byte` sequences where the text is converted into the equivalent UTF8 byte representation. Specifically the compiler will allow for implicit conversions from `string` constants to `byte[]`, `Span<byte>`, and `ReadOnlySpan<byte>`.
```c#
byte[] array = "hello"; // new byte[] { 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20 }
Span<byte> span = "dog"; // new byte[] { 0x64, 0x6f, 0x67 }
ReadOnlySpan<byte> span = "cat"; // new byte[] { 0x63, 0x61, 0x74 }
```
When the input text for the conversion is a malformed UTF16 string then the language will emit an error:
```c#
const string text = "hello \uD801\uD802";
byte[] bytes = text; // Error: the input string is not valid UTF16
```
The predominant usage of this feature is expected to be with literals but it will work with any `string` constant value.
```c#
const string data = "dog"
ReadOnlySpan<byte> span = data; // new byte[] { 0x64, 0x6f, 0x67 }
```
In the case of any constant operation on strings, such as `+`, the encoding to UTF8 will occur on the final `string` vs. happening for the individual parts and then concatenating the results. This ordering is important to consider because it can impact whether or not the conversion succeeds.
```c#
const string first = "\uD83D"; // high surrogate
const string second = "\uDE00"; // low surrogate
ReadOnlySpan<byte> span = first + second;
```
The two parts here are invalid on their own as they are incomplete portions of a surrogate pair. Individually there is no correct translation to UTF8 but together they form a complete surrogate pair that can be successfully translated to UTF8.
Once implemented string literals will have the same problem that other literals have in the language: what type they represent depends on how they are used. C# provides a literal suffix to disambiguate the meaning for other literals. For example developers can write `3.14f` to force the value to be a `float` or `1l` to force the value to be a `long`. Similarly the language will provide the `u8` suffix on string literals to force the type to be UTF8.
When the `u8` suffix is used the literal can still be converted to any of the allowed types: `byte[]`, `Span<byte>` or `ReadOnlySpan<byte>`. The natural type though will be `ReadOnlySpan<byte>`.
```c#
string s1 = "hello"u8; // Error
var s2 = "hello"u8; // Okay and type is ReadOnlySpan<byte>
Span<byte> s3 = "hello"u8; // Okay
byte[] s4 = "hello"u8; // Okay
```
While the inputs to these conversions are constants and the data is fully encoded at compile time, the conversion is **not** considered constant by the language. That is because arrays are not constant today. If the definition of `const` is expanded in the future to consider arrays then this conversion should also be considered. Practically though this means a UTF8 literal cannot be used as the default value of an optional parameter.
```c#
// Error: The argument is not constant
void Write(ReadOnlySpan<byte> message = "missing") { ... }
```
The language will lower the UTF8 encoded strings exactly as if the developer had typed the resulting `byte[]` literal in code. For example:
```c#
ReadOnlySpan<byte> span = "hello";
// Equivalent to
ReadOnlySpan<byte> span = new byte[] { 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20 };
```
That means all optimizations that apply to the `new byte[] { ... }` form will apply to utf8 literals as well. This means the call site will be allocation free as C# will optimize this be stored in the `.data` section of the PE file.
## Drawbacks
### Relying on core APIs
The compiler implementation will use `UTF8Encoding` for both invalid string detection as well as translation to `byte[]`. The exact APIs will possibly depend on which target framework the compiler is using. But `UTF8Encoding` will be the workhorse of the implementation.
Historically the compiler has avoided using runtime APIs for literal processing. That is because it takes control of how constants are processed away from the language and into the runtime. Concretely it means items like bug fixes can change constant encoding and mean that the outcome of C# compilation depends on which runtime the compiler is executing on.
This is not a hypothetical problem. Early versions of Roslyn used `double.Parse` to handle floating point constant parsing. That caused a number of problems. First it meant that some floating point values had different representations between the native compiler and Roslyn. Second as .NET core envolved and fixed long standing bugs in the `double.Parse` code it meant that the meaning of those constants changed in the language depending on what runtime the compiler executed on. As a result the compiler ended up writing it's own version of floating point parsing code and removing the dependency on `double.Parse`.
This scenario was discussed with the runtime team and we do not feel it has the same problems we've hit before. The UTF8 parsing is stable across runtimes and there are no known issues in this area that are areas for future compat concerns. If one does come up we can re-evaluate the strategy.
## Alternatives
### Target type only
The design could rely on target typing only and remove the `u8` suffix on `string` literals. In the majority of cases today the `string` literal is being assigned directly to a `ReadOnlySpan<byte>` hence it's unnecessary.
```c#
ReadOnlySpan<byte> span = "Hello World;
```
The `u8` suffix exists primarily to support two scenarios: `var` and overload resolution. For the latter consider the following use case:
```c#
void Write(ReadOnlySpan<byte> span) { ... }
void Write(string s) {
var bytes = Encoding.Utf8.GetBytes(s);
Write(bytes.AsSpan());
}
```
Given the implementation it is better to call `Write(ReadOnlySpan<byte>)` and the `u8` suffix makes this convenient: `Write("hello"u8)`. Lacking that developers need to resort to awkward casting `Write((ReadOnlySpan<byte>)"hello")`.
Still this is a convenience item, the feature can exist without it and it is non-breaking to add it at a later time.
### Wait for Utf8String type
While the .NET ecosystem is standardizing on `ReadOnlySpan<byte>` as the defacto Utf8 string type today it's possible the runtime will introduce an actual `Utf8String` type is the future.
We should evaluate our design here in the face of this possible change and reflect on whether we'd regret the decisions we've made. This should be weighed though against the realistic probability we'll introduce `Utf8String`, a probability which seems to decrease every day we find `ReadOnlySpan<byte>` as an acceptable alternative.
It seems unlikely that we would regret the target type conversion between string literals and `ReadOnlySpan<byte>`. The use of `ReadOnlySpan<byte>` as utf8 is embedded in our APIs now and hence there is still value in the conversion even if `Utf8String` comes along and is a "better" type. The language could simply prefer conversions to `Utf8String` over `ReadOnlySpan<byte>`.
It seems more likely that we'd regret the `u8` suffix pointing to `ReadOnlySpan<byte>` instead of `Utf8String`. It would be similar to how we regret that `stackalloc int[]` has a natural type of `int*` instead of `Span<int>`. This is not a deal breaker though, just an inconvenience.
## Unresolved questions
### Depth of the conversion
Will it also work anywhere that a byte[] could work? Consider:
```c#
static readonly ReadOnlyMemory<byte> s_data1 = "Data"u8;
static readonly ReadOnlyMemory<byte> s_data2 = "Data";
```
The first example likely should work because of the natural type that comes from `u8`.
The second example is hard to make work because it requires conversions in both directions. That is unless we add `ReadOnlyMemory<byte>` as one of the allowed conversion types.
### Overload resolution breaks
The following API would become ambiguous:
```c#
M("");
static void M1(char[] charArray) => ...;
static void M1(byte[] charArray) => ...;
```
What should we do to address this?
## Examples today
Examples of where runtime has manually encoded the UTF8 bytes today
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/Common/src/System/Net/Http/aspnetcore/Http2/Hpack/StatusCodes.cs#L13-L78
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Memory/src/System/Buffers/Text/Base64Encoder.cs#L581-L591
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpResponseStream.Windows.cs#L284
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs#L30
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs#L852
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs#L35-L42
Examples where we leave perf on the table
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Managed/SafeChannelBindingHandle.cs#L16-L17
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs#L37-L43
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs#L78
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpCommands.cs#L669-L687
## Design meetings
<!-- Link to design notes that affect this proposal, and describe in one sentence for each what changes they led to. -->

View file

@ -142,7 +142,7 @@ Note that this implicit conversion seemingly violates the advice in the beginnin
The following example illustrates implicit dynamic conversions:
```csharp
object o = "object"
object o = "object";
dynamic d = "dynamic";
string s1 = o; // Fails at compile-time -- no conversion exists

View file

@ -41,7 +41,7 @@ Exceptions that occur during destructor execution are worth special mention. If
The following exceptions are thrown by certain C# operations.
| | |
| Exception type | Description |
|--------------------------------------|----------------|
| `System.ArithmeticException` | A base class for exceptions that occur during arithmetic operations, such as `System.DivideByZeroException` and `System.OverflowException`. |
| `System.ArrayTypeMismatchException` | Thrown when a store into an array fails because the actual type of the stored element is incompatible with the actual type of the array. |

View file

@ -2700,16 +2700,15 @@ The predefined multiplication operators are listed below. The operators all comp
The product is computed according to the rules of IEEE 754 arithmetic. The following table lists the results of all possible combinations of nonzero finite values, zeros, infinities, and NaN's. In the table, `x` and `y` are positive finite values. `z` is the result of `x * y`. If the result is too large for the destination type, `z` is infinity. If the result is too small for the destination type, `z` is zero.
| | | | | | | | |
|:----:|-----:|:----:|:---:|:---:|:----:|:----:|:----|
| | +y | -y | +0 | -0 | +inf | -inf | NaN |
| +x | +z | -z | +0 | -0 | +inf | -inf | NaN |
| -x | -z | +z | -0 | +0 | -inf | +inf | NaN |
| +0 | +0 | -0 | +0 | -0 | NaN | NaN | NaN |
| -0 | -0 | +0 | -0 | +0 | NaN | NaN | NaN |
| +inf | +inf | -inf | NaN | NaN | +inf | -inf | NaN |
| -inf | -inf | +inf | NaN | NaN | -inf | +inf | NaN |
| NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| | +y | -y | +0 | -0 | +inf | -inf | NaN |
|:--------:|-----:|:----:|:---:|:---:|:----:|:----:|:----|
| **+x** | +z | -z | +0 | -0 | +inf | -inf | NaN |
| **-x** | -z | +z | -0 | +0 | -inf | +inf | NaN |
| **+0** | +0 | -0 | +0 | -0 | NaN | NaN | NaN |
| **-0** | -0 | +0 | -0 | +0 | NaN | NaN | NaN |
| **+inf** | +inf | -inf | NaN | NaN | +inf | -inf | NaN |
| **-inf** | -inf | +inf | NaN | NaN | -inf | +inf | NaN |
| **NaN** | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
* Decimal multiplication:
@ -2752,16 +2751,15 @@ The predefined division operators are listed below. The operators all compute th
The quotient is computed according to the rules of IEEE 754 arithmetic. The following table lists the results of all possible combinations of nonzero finite values, zeros, infinities, and NaN's. In the table, `x` and `y` are positive finite values. `z` is the result of `x / y`. If the result is too large for the destination type, `z` is infinity. If the result is too small for the destination type, `z` is zero.
| | | | | | | | |
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
| | +y | -y | +0 | -0 | +inf | -inf | NaN |
| +x | +z | -z | +inf | -inf | +0 | -0 | NaN |
| -x | -z | +z | -inf | +inf | -0 | +0 | NaN |
| +0 | +0 | -0 | NaN | NaN | +0 | -0 | NaN |
| -0 | -0 | +0 | NaN | NaN | -0 | +0 | NaN |
| +inf | +inf | -inf | +inf | -inf | NaN | NaN | NaN |
| -inf | -inf | +inf | -inf | +inf | NaN | NaN | NaN |
| NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| | +y | -y | +0 | -0 | +inf | -inf | NaN |
|:--------:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
| **+x** | +z | -z | +inf | -inf | +0 | -0 | NaN |
| **-x** | -z | +z | -inf | +inf | -0 | +0 | NaN |
| **+0** | +0 | -0 | NaN | NaN | +0 | -0 | NaN |
| **-0** | -0 | +0 | NaN | NaN | -0 | +0 | NaN |
| **+inf** | +inf | -inf | +inf | -inf | NaN | NaN | NaN |
| **-inf** | -inf | +inf | -inf | +inf | NaN | NaN | NaN |
| **NaN** | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
* Decimal division:
@ -2802,16 +2800,15 @@ The predefined remainder operators are listed below. The operators all compute t
The following table lists the results of all possible combinations of nonzero finite values, zeros, infinities, and NaN's. In the table, `x` and `y` are positive finite values. `z` is the result of `x % y` and is computed as `x - n * y`, where `n` is the largest possible integer that is less than or equal to `x / y`. This method of computing the remainder is analogous to that used for integer operands, but differs from the IEEE 754 definition (in which `n` is the integer closest to `x / y`).
| | | | | | | | |
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
| | +y | -y | +0 | -0 | +inf | -inf | NaN |
| +x | +z | +z | NaN | NaN | x | x | NaN |
| -x | -z | -z | NaN | NaN | -x | -x | NaN |
| +0 | +0 | +0 | NaN | NaN | +0 | +0 | NaN |
| -0 | -0 | -0 | NaN | NaN | -0 | -0 | NaN |
| +inf | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| -inf | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| | +y | -y | +0 | -0 | +inf | -inf | NaN |
|:--------:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
| **+x** | +z | +z | NaN | NaN | x | x | NaN |
| **-x** | -z | -z | NaN | NaN | -x | -x | NaN |
| **+0** | +0 | +0 | NaN | NaN | +0 | +0 | NaN |
| **-0** | -0 | -0 | NaN | NaN | -0 | -0 | NaN |
| **+inf** | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| **-inf** | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| **NaN** | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
* Decimal remainder:
@ -2850,15 +2847,14 @@ The predefined addition operators are listed below. For numeric and enumeration
The sum is computed according to the rules of IEEE 754 arithmetic. The following table lists the results of all possible combinations of nonzero finite values, zeros, infinities, and NaN's. In the table, `x` and `y` are nonzero finite values, and `z` is the result of `x + y`. If `x` and `y` have the same magnitude but opposite signs, `z` is positive zero. If `x + y` is too large to represent in the destination type, `z` is an infinity with the same sign as `x + y`.
| | | | | | | |
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
| | y | +0 | -0 | +inf | -inf | NaN |
| x | z | x | x | +inf | -inf | NaN |
| +0 | y | +0 | +0 | +inf | -inf | NaN |
| -0 | y | +0 | -0 | +inf | -inf | NaN |
| +inf | +inf | +inf | +inf | +inf | NaN | NaN |
| -inf | -inf | -inf | -inf | NaN | -inf | NaN |
| NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| | y | +0 | -0 | +inf | -inf | NaN |
|:--------:|:----:|:----:|:----:|:----:|:----:|:----:|
| **x** | z | x | x | +inf | -inf | NaN |
| **+0** | y | +0 | +0 | +inf | -inf | NaN |
| **-0** | y | +0 | -0 | +inf | -inf | NaN |
| **+inf** | +inf | +inf | +inf | +inf | NaN | NaN |
| **-inf** | -inf | -inf | -inf | NaN | -inf | NaN |
| **NaN** | NaN | NaN | NaN | NaN | NaN | NaN |
* Decimal addition:
@ -2943,15 +2939,14 @@ The predefined subtraction operators are listed below. The operators all subtrac
The difference is computed according to the rules of IEEE 754 arithmetic. The following table lists the results of all possible combinations of nonzero finite values, zeros, infinities, and NaNs. In the table, `x` and `y` are nonzero finite values, and `z` is the result of `x - y`. If `x` and `y` are equal, `z` is positive zero. If `x - y` is too large to represent in the destination type, `z` is an infinity with the same sign as `x - y`.
| | | | | | | |
|:----:|:----:|:----:|:----:|:----:|:----:|:---:|
| | y | +0 | -0 | +inf | -inf | NaN |
| x | z | x | x | -inf | +inf | NaN |
| +0 | -y | +0 | +0 | -inf | +inf | NaN |
| -0 | -y | -0 | +0 | -inf | +inf | NaN |
| +inf | +inf | +inf | +inf | NaN | +inf | NaN |
| -inf | -inf | -inf | -inf | -inf | NaN | NaN |
| NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| | y | +0 | -0 | +inf | -inf | NaN |
|:--------:|:----:|:----:|:----:|:----:|:----:|:---:|
| **x** | z | x | x | -inf | +inf | NaN |
| **+0** | -y | +0 | +0 | -inf | +inf | NaN |
| **-0** | -y | -0 | +0 | -inf | +inf | NaN |
| **+inf** | +inf | +inf | +inf | NaN | +inf | NaN |
| **-inf** | -inf | -inf | -inf | -inf | NaN | NaN |
| **NaN** | NaN | NaN | NaN | NaN | NaN | NaN |
* Decimal subtraction:
@ -3362,7 +3357,7 @@ where `x` is an expression of a nullable type, if operator overload resolution (
The `is` operator is used to dynamically check if the run-time type of an object is compatible with a given type. The result of the operation `E is T`, where `E` is an expression and `T` is a type, is a boolean value indicating whether `E` can successfully be converted to type `T` by a reference conversion, a boxing conversion, or an unboxing conversion. The operation is evaluated as follows, after type arguments have been substituted for all type parameters:
* If `E` is an anonymous function, a compile-time error occurs
* If `E` is a method group or the `null` literal, of if the type of `E` is a reference type or a nullable type and the value of `E` is null, the result is false.
* If `E` is a method group or the `null` literal, or if the type of `E` is a reference type or a nullable type and the value of `E` is null, the result is false.
* Otherwise, let `D` represent the dynamic type of `E` as follows:
* If the type of `E` is a reference type, `D` is the run-time type of the instance reference by `E`.
* If the type of `E` is a nullable type, `D` is the underlying type of that nullable type.
@ -4397,7 +4392,7 @@ GroupJoin(orders, c => c.CustomerID, o => o.CustomerID,
(c, co) => new { c, co }).
Select(x => new { x, n = x.co.Count() }).
Where(y => y.n >= 10).
Select(y => new { y.x.c.Name, OrderCount = y.n)
Select(y => new { y.x.c.Name, OrderCount = y.n })
```
where `x` and `y` are compiler generated identifiers that are otherwise invisible and inaccessible.

View file

@ -131,7 +131,7 @@ C#'s value types are further divided into ***simple types***, ***enum types***,
The following table provides an overview of C#'s type system.
| __Category__ | | __Description__ |
| __Category__ | __Types__ | __Description__ |
|-----------------|-----------------|-----------------|
| Value types | Simple types | Signed integral: `sbyte`, `short`, `int`, `long` |
| | | Unsigned integral: `byte`, `ushort`, `uint`, `ulong` |