Merge remote-tracking branch 'refs/remotes/origin/master' into design-notes

This commit is contained in:
Mads Torgersen 2018-05-14 13:33:55 -07:00
commit f9cf91154d
10 changed files with 159 additions and 37 deletions

View file

@ -88,3 +88,14 @@ Features Added in C# Language Versions
- [Non-trailing named arguments](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/non-trailing-named-arguments.md)
- [Private protected accessibility](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/private-protected.md)
- [Digit separator after base specifier](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/leading-separator.md)
# C# 7.3 (Visual Studio 2017 version 15.7)
- `System.Enum`, `System.Delegate` and [`unmanaged`](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/blittable.md) constraints.
- [Ref local re-assignment](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/ref-local-reassignment.md): Ref locals and ref parameters can now be reassigned with the ref assignment operator (`= ref`).
- [Stackalloc initializers](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/stackalloc-array-initializers.md): Stack-allocated arrays can now be initialized, e.g. `Span<int> x = stackalloc[] { 1, 2, 3 };`.
- [Indexing movable fixed buffers](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/indexing-movable-fixed-fields.md): Fixed buffers can be indexed into without first being pinned.
- [Custom `fixed` statement](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/pattern-based-fixed.md): Types that implement a suitable `GetPinnableReference` can be used in a `fixed` statement.
- [Improved overload candidates](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/improved-overload-candidates.md): Some overload resolution candidates can be ruled out early, thus reducing ambiguities.
- [Expression variables in initializers and queries](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/expression-variables-in-initializers.md): Expression variables like `out var` and pattern variables are allowed in field initializers, constructor initializers and LINQ queries.
- [Tuple comparison](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/tuple-equality.md): Tuples can now be compared with `==` and `!=`.
- [Attributes on backing fields](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/auto-prop-field-attrs.md): Allows `[field: …]` attributes on an auto-implemented property to target its backing field.

View file

@ -1,10 +1,5 @@
# Auto-Implemented Property Field-Targeted Attributes
* [x] Proposed
* [ ] Prototype: Not Started
* [ ] Implementation: Not Started
* [ ] Specification: Not Started
## Summary
[summary]: #summary

View file

@ -1,10 +1,5 @@
# Unmanaged type constraint
* [x] Proposed
* [ ] Prototype
* [ ] Implementation
* [ ] Specification
## Summary
[summary]: #summary

View file

@ -1,10 +1,5 @@
# expression variables in initializers
* [x] Proposed
* [ ] Prototype: None
* [ ] Implementation: None
* [ ] Specification: See below
## Summary
[summary]: #summary

View file

@ -1,10 +1,5 @@
# Improved overload candidates
* [x] Proposed
* [x] Prototype
* [x] Implementation
* [ ] Specification
## Summary
[summary]: #summary

View file

@ -1,10 +1,5 @@
# Pattern-based `fixed` statement
* [x] Proposed
* [ ] Prototype: Not Started
* [ ] Implementation: Not Started
* [ ] Specification: Not Started
## Summary
[summary]: #summary
@ -33,9 +28,9 @@ It would be desirable to be able to switch to something more flexible that decou
[design]: #detailed-design
## *Pattern* ##
A viable pattern-based “fixed” need to:
A viable pattern-based “fixed” need to:
- Provide the managed references to pin the instance and to initialize the pointer (preferably this is the same reference)
- Convey unambiguously the type of the unmanaged element (i.e. “char” for “string”)
- Convey unambiguously the type of the unmanaged element (i.e. “char” for “string”)
- Prescribe the behavior in "empty" case when there is nothing to refer to.
- Should not push API authors toward design decisions that hurt the use of the type outside of `fixed`.
@ -49,8 +44,8 @@ In order to be used by the `fixed` statement the following conditions must be me
(`readonly` is permitted so that authors of immutable/readonly types could implement the pattern without adding writeable API that could be used in safe code)
1) T is an unmanaged type.
(since `T*` becomes the pointer type. The restriction will naturally expand if/when the notion of "unmanaged" is expanded)
1) Returns managed `nullptr` when there is no data to pin probably the cheapest way to convey emptiness.
(note that “” string returns a ref to '\0' since strings are null-terminated)
1) Returns managed `nullptr` when there is no data to pin probably the cheapest way to convey emptiness.
(note that “” string returns a ref to '\0' since strings are null-terminated)
Alternatively for the `#3` we can allow the result in empty cases be undefined or implementation-specific.
That, however, may make the API more dangerous and prone to abuse and unintended compatibility burdens.
@ -128,4 +123,4 @@ There is no solution for `System.String` if alternative solution is desired.
## Design meetings
None yet.
None yet.

View file

@ -1,10 +1,5 @@
# Stackalloc array initializers.
* [x] Proposed
* [ ] Prototype: Not Started
* [ ] Implementation: Not Started
* [ ] Specification: Not Started
## Summary
[summary]: #summary
@ -62,4 +57,4 @@ This is a convenience feature. It is possible to just do nothing.
## Design meetings
None yet.
None yet.

82
proposals/ranges.cs Normal file
View file

@ -0,0 +1,82 @@
namespace System
{
public readonly struct Index
{
private readonly int _value;
public int Value => _value < 0 ? ~_value : _value;
public bool FromEnd => _value < 0;
public Index(int value, bool fromEnd)
{
if (value < 0) throw new ArgumentException("Index must not be negative.", nameof(value));
_value = fromEnd ? ~value : value;
}
public static implicit operator Index(int value)
=> new Index(value, fromEnd: false);
}
public readonly struct Range
{
public Index Start { get; }
public Index End { get; }
private Range(Index start, Index end)
{
this.Start = start;
this.End = end;
}
public static Range Create(Index start, Index end) => new Range(start, end);
public static Range FromStart(Index start) => new Range(start, new Index(0, fromEnd: true));
public static Range ToEnd(Index end) => new Range(new Index(0, fromEnd: false), end);
public static Range All() => new Range(new Index(0, fromEnd: false), new Index(0, fromEnd: true));
}
static class Extensions
{
public static int get_IndexerExtension(this int[] array, Index index) =>
index.FromEnd ? array[array.Length - index.Value] : array[index.Value];
public static int get_IndexerExtension(this Span<int> span, Index index) =>
index.FromEnd ? span[span.Length - index.Value] : span[index.Value];
public static char get_IndexerExtension(this string s, Index index) =>
index.FromEnd ? s[s.Length - index.Value] : s[index.Value];
public static Span<int> get_IndexerExtension(this int[] array, Range range) =>
array.Slice(range);
public static Span<int> get_IndexerExtension(this Span<int> span, Range range) =>
span.Slice(range);
public static string get_IndexerExtension(this string s, Range range) =>
s.Substring(range);
public static Span<T> Slice<T>(this T[] array, Range range)
=> array.AsSpan().Slice(range);
public static Span<T> Slice<T>(this Span<T> span, Range range)
{
var (start, length) = GetStartAndLength(range, span.Length);
return span.Slice(start, length);
}
public static string Substring(this string s, Range range)
{
var (start, length) = GetStartAndLength(range, s.Length);
return s.Substring(start, length);
}
private static (int start, int length) GetStartAndLength(Range range, int entityLength)
{
var start = range.Start.FromEnd ? entityLength - range.Start.Value : range.Start.Value;
var end = range.End.FromEnd ? entityLength - range.End.Value : range.End.Value;
var length = end - start;
return (start, length);
}
}
}

60
proposals/ranges.md Normal file
View file

@ -0,0 +1,60 @@
# Ranges
## Summary
This feature is about delivering two new operators that allow constructing `System.Index` and `System.Range` objects, and using them to index/slice collections at runtime.
## Detailed Design
#### System.Index
C# has no way of indexing a collection from the end, but rather most indexers use the "from start" notion, or do a "length - i" expression. We introduce a new Index expression that means "from the end". The feature will introduce a new unary prefix "hat" operator. Its single operand must be convertible to `System.Int32`. It will be lowered into the appropriate `System.Index` factory method call.
```csharp
var thirdItem = list[2]; // list[2]
var lastItem = list[^1]; // list[Index.CreateFromEnd(1)]
var multiDimensional = list[3, ^2] // list[3, Index.CreateFromEnd(2)]
```
#### System.Range
C# has no syntactic way to access "ranges" or "slices" of collections. Usually users are forced to implement complex structures to filter/operate on slices of memory, or resort to LINQ methods like `list.Skip(5).Take(2)`. With the addition of `System.Span<T>` and other similar types, it becomes more important to have this kind of operation supported on a deeper level in the language/runtime, and have the interface unified.
The language will introduce a new range operator `x..y`. It is a binary infix operator that accepts two expressions. Either operands can be omitted (examples below), and they have to be convertible to `System.Index`. It will be lowered to the appropriate `System.Range` factory method call.
```csharp
var slice1 = list[2..^3]; // list[Range.Create(2, Index.CreateFromEnd(3))]
var slice2 = list[..^3]; // list[Range.ToEnd(Index.CreateFromEnd(3))]
var slice3 = list[2..]; // list[Range.FromStart(2)]
var slice4 = list[..]; // list[Range.All]
var multiDimensional = list[1..2, ..] // list[Range.Create(1, 2), Range.All]
```
Moreover, `System.Index` should have an implicit conversion from `System.Int32`, in order not to need to overload for mixing integers and indexes over multi-dimensional signatures.
## Workarounds
For prototyping reasons, and since runtime/framework collections will not have support for such indexers, the compiler will finally look for the following extension method when doing overload resolution:
* `op_Indexer_Extension(this TCollection<TItem> collection, ...arguments supplied to the indexer)`
This workaround will be removed once contract with runtime/framework is finalized, and before the feature is declared complete.
## Alternatives
The new operators (`^` and `..`) are syntactic sugar. The functionality can be implemented by explicit calls to `System.Index` and `System.Range` factory methods, but it will result in a lot more boilerplate code, and the experience will be unintuitive.
## IL Representation
These two operators will be lowered to regular indexer/method calls, with no change in subsequent compiler layers.
## Runtime behaviour
* Compiler can optimize indexers for built-in types like arrays and strings, and lower the indexing to the appropriate existing methods.
* `System.Index` will throw if constructed with a negative value.
* `^0` does not throw, but it translates to the length of the collection/enumerable it is supplied to.
* `Range.All` is semantically equivalent to `0..^0`, and can be deconstructed to these indices.
## Questions

View file

@ -376,7 +376,6 @@ static void Main(string[] args) {
default:
Console.WriteLine("{0} arguments", n);
break;
}
}
}
```