Skip to content

Commit

Permalink
Merging inferred tuple names feature into master branch
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv authored Apr 28, 2017
2 parents e7b7e08 + 5734799 commit 760d254
Show file tree
Hide file tree
Showing 74 changed files with 3,612 additions and 296 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ In C# 7, the compiler accepted a pattern of the form `dynamic identifier`, e.g.

- https://github.com/dotnet/roslyn/issues/17173 Before C# 7.1, `csc` would accept leading zeroes in the `/langversion` option. Now it should reject it. Example: `csc.exe source.cs /langversion:07`.

- https://github.com/dotnet/roslyn/issues/16870 In C# 7.0 and before C# 7.1, the compiler accepted self-assignments in deconstruction-assignment. The compiler now produces a warning for that. For instance, in `(x, y) = (x, 2);`.
- https://github.com/dotnet/csharplang/issues/415
In C# 7.0, elements in tuple literals can be named explicitly, but in C# 7.1, elements that aren't named explicitly will get an inferred named. This uses the same rules as members in anonymous types which aren't named explicitly.
For instance, `var t = (a, b.c, this.d);` will produce a tuple with element names "a", "c" and "d". As a result, an invocation on a tuple member may result in a different result than it did in C# 7.0.
Consider the case where the type of `a` is `System.Func<bool>` and you write `var local = t.a();`. This will now find the first element of the tuple and invoke it, whereas previously it could only mean "invoke an extension method named 'a'".

- https://github.com/dotnet/roslyn/issues/16870 In C# 7.0 and before C# 7.1, the compiler accepted self-assignments in deconstruction-assignment. The compiler now produces a warning for that. For instance, in `(x, y) = (x, 2);`.
2 changes: 1 addition & 1 deletion docs/compilers/Visual Basic/CommandLine.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
| **LANGUAGE**
| `/define:`*symbol_list* | Declare global conditional compilation symbol(s). *symbol_list* is *name*`=`*value*`,`... (Short form: `/d`)
| `/imports:`*import_list* | Declare global Imports for namespaces in referenced metadata files. *import_list* is *namespace*`,`...
| `/langversion:`*number* | Specify language version: `9`&#124;`9.0`&#124;`10`&#124;`10.0`&#124;`11`&#124;`11.0`&#124;`12`&#124;`12.0`&#124;`13`&#124;`13.0`&#124;`14`&#124;`14.0`. The default is `14`.
| `/langversion:`*number* | Specify language version: `9`&#124;`9.0`&#124;`10`&#124;`10.0`&#124;`11`&#124;`11.0`&#124;`12`&#124;`12.0`&#124;`13`&#124;`13.0`&#124;`14`&#124;`14.0`&#124;`15`&#124;`15.0`&#124;`15.3`. The default is `15` and the latest is `15.3`.
| `/optionexplicit`{`+`&#124;`-`} | Require explicit declaration of variables.
| `/optioninfer`{`+`&#124;`-`} | Allow type inference of variables.
| `/rootnamespace`:*string* | Specifies the root Namespace for all top-level type declarations.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Changes since VS2017 (VB 15)
===========================

- https://github.com/dotnet/csharplang/issues/415
In VB 15, elements in tuple literals can be named explicitly, but in VB 15.3, elements that aren't named explicitly will get an inferred named. This uses the same rules as members in anonymous types which aren't named explicitly.
For instance, `Dim t = (a, b.c, Me.d)` will produce a tuple with element names "a", "c" and "d". As a result, an invocation on a tuple member may result in a different result than it did in VB 15.
Consider the case where the type of `a` is `System.Func(Of Boolean)` and you write `Dim local = t.a()`. This will now find the first element of the tuple and invoke it, whereas previously it could only mean "invoke an extension method named 'a'".
37 changes: 32 additions & 5 deletions src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslyn.Utilities;
Expand Down Expand Up @@ -461,7 +462,8 @@ private static TypeSymbol MakeMergedTupleType(ArrayBuilder<DeconstructionVariabl
elementNames: default(ImmutableArray<string>),
compilation: compilation,
diagnostics: diagnostics,
shouldCheckConstraints: false);
shouldCheckConstraints: false,
errorPositions: default(ImmutableArray<bool>));
}

private BoundTupleLiteral DeconstructionVariablesAsTuple(CSharpSyntaxNode syntax, ArrayBuilder<DeconstructionVariable> variables, DiagnosticBag diagnostics, bool hasErrors)
Expand All @@ -470,30 +472,55 @@ private BoundTupleLiteral DeconstructionVariablesAsTuple(CSharpSyntaxNode syntax
var valuesBuilder = ArrayBuilder<BoundExpression>.GetInstance(count);
var typesBuilder = ArrayBuilder<TypeSymbol>.GetInstance(count);
var locationsBuilder = ArrayBuilder<Location>.GetInstance(count);
var namesBuilder = ArrayBuilder<string>.GetInstance(count);
foreach (var variable in variables)
{
if (variable.HasNestedVariables)
{
var nestedTuple = DeconstructionVariablesAsTuple(variable.Syntax, variable.NestedVariables, diagnostics, hasErrors);
valuesBuilder.Add(nestedTuple);
typesBuilder.Add(nestedTuple.Type);
namesBuilder.Add(null);
}
else
{
var single = variable.Single;
valuesBuilder.Add(single);
typesBuilder.Add(single.Type);
namesBuilder.Add(ExtractDeconstructResultElementName(single));
}
locationsBuilder.Add(variable.Syntax.Location);
}

var uniqueFieldNames = PooledHashSet<string>.GetInstance();
RemoveDuplicateInferredTupleNames(namesBuilder, uniqueFieldNames);
uniqueFieldNames.Free();
ImmutableArray<string> tupleNames = namesBuilder.ToImmutableAndFree();

bool disallowInferredNames = this.Compilation.LanguageVersion.DisallowInferredTupleElementNames();
var inferredPositions = tupleNames.SelectAsArray(n => n != null);

var type = TupleTypeSymbol.Create(syntax.Location,
typesBuilder.ToImmutableAndFree(), locationsBuilder.ToImmutableAndFree(),
elementNames: default(ImmutableArray<string>), compilation: this.Compilation,
shouldCheckConstraints: !hasErrors, syntax: syntax, diagnostics: hasErrors ? null : diagnostics);
tupleNames, this.Compilation,
shouldCheckConstraints: !hasErrors,
errorPositions: disallowInferredNames ? inferredPositions : default(ImmutableArray<bool>),
syntax: syntax, diagnostics: hasErrors ? null : diagnostics);

// Always track the inferred positions in the bound node, so that conversions don't produce a warning
// for "dropped names" on tuple literal when the name was inferred.
return new BoundTupleLiteral(syntax, tupleNames, inferredPositions, arguments: valuesBuilder.ToImmutableAndFree(), type: type);
}

/// <summary>Extract inferred name from a single deconstruction variable.</summary>
private static string ExtractDeconstructResultElementName(BoundExpression expression)
{
if (expression.Kind == BoundKind.DiscardExpression)
{
return null;
}

return new BoundTupleLiteral(syntax: syntax, argumentNamesOpt: default(ImmutableArray<string>),
arguments: valuesBuilder.ToImmutableAndFree(), type: type);
return InferTupleElementName(expression.Syntax);
}

/// <summary>
Expand Down
182 changes: 155 additions & 27 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -742,23 +742,37 @@ private BoundExpression BindDeclarationVariables(TypeSymbol declType, VariableDe
case SyntaxKind.ParenthesizedVariableDesignation:
{
var tuple = (ParenthesizedVariableDesignationSyntax)node;
var builder = ArrayBuilder<BoundExpression>.GetInstance(tuple.Variables.Count);
int count = tuple.Variables.Count;
var builder = ArrayBuilder<BoundExpression>.GetInstance(count);
var namesBuilder = ArrayBuilder<string>.GetInstance(count);

foreach (var n in tuple.Variables)
{
builder.Add(BindDeclarationVariables(declType, n, n, diagnostics));
namesBuilder.Add(InferTupleElementName(n));
}
var subExpressions = builder.ToImmutableAndFree();

var uniqueFieldNames = PooledHashSet<string>.GetInstance();
RemoveDuplicateInferredTupleNames(namesBuilder, uniqueFieldNames);
uniqueFieldNames.Free();

var tupleNames = namesBuilder.ToImmutableAndFree();
var inferredPositions = tupleNames.SelectAsArray(n => n != null);
bool disallowInferredNames = this.Compilation.LanguageVersion.DisallowInferredTupleElementNames();

// We will not check constraints at this point as this code path
// is failure-only and the caller is expected to produce a diagnostic.
var tupleType = TupleTypeSymbol.Create(
null,
subExpressions.SelectAsArray(e => e.Type),
default(ImmutableArray<Location>),
default(ImmutableArray<string>),
tupleNames,
Compilation,
shouldCheckConstraints: false);
return new BoundTupleLiteral(syntax, default(ImmutableArray<string>), subExpressions, tupleType);
shouldCheckConstraints: false,
errorPositions: disallowInferredNames ? inferredPositions : default(ImmutableArray<bool>));

return new BoundTupleLiteral(syntax, default(ImmutableArray<string>), inferredPositions, subExpressions, tupleType);
}
default:
throw ExceptionUtilities.UnexpectedValue(node.Kind());
Expand All @@ -780,41 +794,30 @@ private BoundExpression BindTupleExpression(TupleExpressionSyntax node, Diagnost
return BadExpression(node, args);
}

bool hasErrors = false;
bool hasNaturalType = true;

// set of names already used
var uniqueFieldNames = PooledHashSet<string>.GetInstance();

var boundArguments = ArrayBuilder<BoundExpression>.GetInstance(arguments.Count);
var elementTypes = ArrayBuilder<TypeSymbol>.GetInstance(arguments.Count);
var elementLocations = ArrayBuilder<Location>.GetInstance(arguments.Count);
ArrayBuilder<string> elementNames = null;

// prepare and check element names and types
// prepare names
var (elementNames, inferredPositions, hasErrors) = ExtractTupleElementNames(arguments, diagnostics);

// prepare types and locations
for (int i = 0; i < numElements; i++)
{
ArgumentSyntax argumentSyntax = arguments[i];
string name = null;
IdentifierNameSyntax nameSyntax = argumentSyntax.NameColon?.Name;

if (nameSyntax != null)
{
name = nameSyntax.Identifier.ValueText;
elementLocations.Add(nameSyntax.Location);

if (!CheckTupleMemberName(name, i, argumentSyntax.NameColon.Name, diagnostics, uniqueFieldNames))
{
hasErrors = true;
}
}
else
{
elementLocations.Add(argumentSyntax.Location);
}

CollectTupleFieldMemberNames(name, i + 1, numElements, ref elementNames);

BoundExpression boundArgument = BindValue(argumentSyntax.Expression, diagnostics, BindValueKind.RValue);
if (boundArgument.Type?.SpecialType == SpecialType.System_Void)
{
Expand All @@ -835,26 +838,151 @@ private BoundExpression BindTupleExpression(TupleExpressionSyntax node, Diagnost
}
}

uniqueFieldNames.Free();

var elementNamesArray = elementNames == null ?
default(ImmutableArray<string>) :
elementNames.ToImmutableAndFree();

NamedTypeSymbol tupleTypeOpt = null;
var elements = elementTypes.ToImmutableAndFree();
var locations = elementLocations.ToImmutableAndFree();

if (hasNaturalType)
{
tupleTypeOpt = TupleTypeSymbol.Create(node.Location, elements, locations, elementNamesArray, this.Compilation, syntax: node, diagnostics: diagnostics, shouldCheckConstraints: true);
bool disallowInferredNames = this.Compilation.LanguageVersion.DisallowInferredTupleElementNames();

tupleTypeOpt = TupleTypeSymbol.Create(node.Location, elements, locations, elementNames,
this.Compilation, syntax: node, diagnostics: diagnostics, shouldCheckConstraints: true,
errorPositions: disallowInferredNames ? inferredPositions : default(ImmutableArray<bool>));
}
else
{
TupleTypeSymbol.VerifyTupleTypePresent(elements.Length, node, this.Compilation, diagnostics);
}

return new BoundTupleLiteral(node, elementNamesArray, boundArguments.ToImmutableAndFree(), tupleTypeOpt, hasErrors);
// Always track the inferred positions in the bound node, so that conversions don't produce a warning
// for "dropped names" on tuple literal when the name was inferred.
return new BoundTupleLiteral(node, elementNames, inferredPositions, boundArguments.ToImmutableAndFree(), tupleTypeOpt, hasErrors);
}

private static (ImmutableArray<string> elementNamesArray, ImmutableArray<bool> inferredArray, bool hasErrors) ExtractTupleElementNames(
SeparatedSyntaxList<ArgumentSyntax> arguments, DiagnosticBag diagnostics)
{
bool hasErrors = false;
int numElements = arguments.Count;
var uniqueFieldNames = PooledHashSet<string>.GetInstance();
ArrayBuilder<string> elementNames = null;
ArrayBuilder<string> inferredElementNames = null;

for (int i = 0; i < numElements; i++)
{
ArgumentSyntax argumentSyntax = arguments[i];
IdentifierNameSyntax nameSyntax = argumentSyntax.NameColon?.Name;

string name = null;
string inferredName = null;

if (nameSyntax != null)
{
name = nameSyntax.Identifier.ValueText;

if (diagnostics != null && !CheckTupleMemberName(name, i, argumentSyntax.NameColon.Name, diagnostics, uniqueFieldNames))
{
hasErrors = true;
}
}
else
{
inferredName = InferTupleElementName(argumentSyntax.Expression);
}

CollectTupleFieldMemberName(name, i, numElements, ref elementNames);
CollectTupleFieldMemberName(inferredName, i, numElements, ref inferredElementNames);
}

RemoveDuplicateInferredTupleNames(inferredElementNames, uniqueFieldNames);
uniqueFieldNames.Free();

var result = MergeTupleElementNames(elementNames, inferredElementNames);
elementNames?.Free();
inferredElementNames?.Free();
return (result.names, result.inferred, hasErrors);
}

private static (ImmutableArray<string> names, ImmutableArray<bool> inferred) MergeTupleElementNames(
ArrayBuilder<string> elementNames, ArrayBuilder<string> inferredElementNames)
{
if (elementNames == null)
{
if (inferredElementNames == null)
{
return (default(ImmutableArray<string>), default(ImmutableArray<bool>));
}
else
{
var finalNames = inferredElementNames.ToImmutable();
return (finalNames, finalNames.SelectAsArray(n => n != null));
}
}

if (inferredElementNames == null)
{
return (elementNames.ToImmutable(), default(ImmutableArray<bool>));
}

Debug.Assert(elementNames.Count == inferredElementNames.Count);
var builder = ArrayBuilder<bool>.GetInstance(elementNames.Count);
for (int i = 0; i < elementNames.Count; i++)
{
string inferredName = inferredElementNames[i];
if (elementNames[i] == null && inferredName != null)
{
elementNames[i] = inferredName;
builder.Add(true);
}
else
{
builder.Add(false);
}
}

return (elementNames.ToImmutable(), builder.ToImmutableAndFree());
}

private static void RemoveDuplicateInferredTupleNames(ArrayBuilder<string> inferredElementNames, HashSet<string> uniqueFieldNames)
{
if (inferredElementNames == null)
{
return;
}

// Inferred names that duplicate an explicit name or a previous inferred name are tagged for removal
var toRemove = PooledHashSet<string>.GetInstance();
foreach (var name in inferredElementNames)
{
if (name != null && !uniqueFieldNames.Add(name))
{
toRemove.Add(name);
}
}

for (int i = 0; i < inferredElementNames.Count; i++)
{
var inferredName = inferredElementNames[i];
if (inferredName != null && toRemove.Contains(inferredName))
{
inferredElementNames[i] = null;
}
}
toRemove.Free();
}

private static string InferTupleElementName(SyntaxNode syntax)
{
string name = syntax.TryGetInferredMemberName();

// Reserved names are never candidates to be inferred names, at any position
if (name == null || TupleTypeSymbol.IsElementNameReserved(name) != -1)
{
return null;
}

return name;
}

private BoundExpression BindRefValue(RefValueExpressionSyntax node, DiagnosticBag diagnostics)
Expand Down
Loading

0 comments on commit 760d254

Please sign in to comment.