Skip to content

Commit

Permalink
Add support for using alias = any_type. (#50167)
Browse files Browse the repository at this point in the history
* Add parsing of using aliases that point to types.

* Update API surface.

* Fix tests

* Add parsing tests

* Add binding support

* Update IDE side.

* Add semantic tests

* Support functptr types

* Disallow ref types

* Merge

* Fix

* Fix

* move more cases to type

* Move error

* Add unsafe modifier

* Bind with unsafe

* No nullable reference types

* Update apis

* Add apis

* Add api

* Add api

* Add api

* Fix

* NRT

* NRT

* NRT

* Fix

* Move

* Add error facts

* Add error facts

* Add tests

* Add tests

* Add c# 11 test

* Add unsafe tests

* Add unsafe tests

* Dynamic work

* Update feature status

* Delete

* Update

* Add comment

* Fix test

* Fix test

* Fix test

* Add tests

* Add tests

* Update docs/Language Feature Status.md

* Add tests

* Add support for top level dynamic

* Add support for top level dynamic

* Fix loc string

* Move into 12.0 section

* Add test

* Add test

* Move errors to binding

* Add tests

* Use file scoped namespace

* Allow aliases to nint/nuint.

* Add tests

* Add tests

* Add comment

* Update test

* 'dynamic' should bind to type

* Update docs/Language Feature Status.md

* Update tests

* PR feedback

* switch to typesymbol

* Simplify

* Add tests

* Add tests

* Named parameter

* Remove unused usings

* Improve tests

* Use RegularPreview

* Update tests

* Fix comment

* Merge tests

* Merge tests

* Add tests

* Syntax test only
  • Loading branch information
CyrusNajmabadi authored Feb 3, 2023
1 parent 8430000 commit c8d3925
Show file tree
Hide file tree
Showing 60 changed files with 4,460 additions and 353 deletions.
1 change: 1 addition & 0 deletions docs/Language Feature Status.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ efforts behind them.
| [Lambda default parameters](https://github.com/dotnet/csharplang/issues/6051) | [lambda-default-parameters](https://github.com/dotnet/roslyn/tree/features/lambda-default-parameters) | [Merged into 17.5p2](https://github.com/dotnet/roslyn/issues/62485) | [adamperlin](https://github.com/adamperlin), [jjonescz](https://github.com/jjonescz) | [333fred](https://github.com/333fred), [cston](https://github.com/cston) | [captainsafia](https://github.com/captainsafia) |
| [Default in deconstruction](https://github.com/dotnet/roslyn/pull/25562) | [decon-default](https://github.com/dotnet/roslyn/tree/features/decon-default) | [In Progress](https://github.com/dotnet/roslyn/issues/25559) | [jcouv](https://github.com/jcouv) | [gafter](https://github.com/gafter) | [jcouv](https://github.com/jcouv) |
| [Collection Literals](https://github.com/dotnet/csharplang/issues/5354) | [CollectionLiterals](https://github.com/dotnet/roslyn/tree/features/CollectionLiterals) | | [cston](https://github.com/cston) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) |
| [Using aliases for any type](https://github.com/dotnet/csharplang/issues/4284) | [UsingAliasTypes](https://github.com/dotnet/roslyn/tree/features/UsingAliasTypes) | [Test Plan](https://github.com/dotnet/roslyn/issues/56323) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [jcouv](https://github.com/jcouv) [cston](https://github.com/cston) | |

# C# 11.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@ private static async Task<CompilationUnitSyntax> ExpandUsingDirectivesAsync(

private static async Task<SyntaxNode> ExpandUsingDirectiveAsync(Document document, UsingDirectiveSyntax usingDirective, CancellationToken cancellationToken)
{
var newName = await Simplifier.ExpandAsync(usingDirective.Name, document, cancellationToken: cancellationToken).ConfigureAwait(false);
return usingDirective.WithName(newName);
var newType = await Simplifier.ExpandAsync(usingDirective.Type, document, cancellationToken: cancellationToken).ConfigureAwait(false);
return usingDirective.WithType(newType);
}

private static CompilationUnitSyntax MoveUsingsInsideNamespace(CompilationUnitSyntax compilationUnit)
Expand Down
150 changes: 102 additions & 48 deletions src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs
Original file line number Diff line number Diff line change
Expand Up @@ -477,10 +477,19 @@ internal NamespaceOrTypeOrAliasSymbolWithAnnotations BindNamespaceOrTypeOrAliasS
{
// ref needs to be handled by the caller
var refTypeSyntax = (RefTypeSyntax)syntax;
var refToken = refTypeSyntax.RefKeyword;
if (!syntax.HasErrors)
{
diagnostics.Add(ErrorCode.ERR_UnexpectedToken, refToken.GetLocation(), refToken.ToString());
var refToken = refTypeSyntax.RefKeyword;

// Specialized diagnostic if our parent is a using directive.
if (refTypeSyntax.Parent is UsingDirectiveSyntax)
{
diagnostics.Add(ErrorCode.ERR_BadRefInUsingAlias, refToken.GetLocation());
}
else
{
diagnostics.Add(ErrorCode.ERR_UnexpectedToken, refToken.GetLocation(), refToken.ToString());
}
}

return BindNamespaceOrTypeOrAliasSymbol(refTypeSyntax.Type, diagnostics, basesBeingResolved, suppressUseSiteDiagnostics);
Expand Down Expand Up @@ -830,29 +839,6 @@ private NamespaceOrTypeOrAliasSymbolWithAnnotations BindSimpleNamespaceOrTypeOrA
}
}

private static bool IsViableType(LookupResult result)
{
if (!result.IsMultiViable)
{
return false;
}

foreach (var s in result.Symbols)
{
switch (s.Kind)
{
case SymbolKind.Alias:
if (((AliasSymbol)s).Target.Kind == SymbolKind.NamedType) return true;
break;
case SymbolKind.NamedType:
case SymbolKind.TypeParameter:
return true;
}
}

return false;
}

protected NamespaceOrTypeOrAliasSymbolWithAnnotations BindNonGenericSimpleNamespaceOrTypeOrAliasSymbol(
IdentifierNameSyntax node,
BindingDiagnosticBag diagnostics,
Expand All @@ -875,7 +861,7 @@ protected NamespaceOrTypeOrAliasSymbolWithAnnotations BindNonGenericSimpleNamesp
}

var errorResult = CreateErrorIfLookupOnTypeParameter(node.Parent, qualifierOpt, identifierValueText, 0, diagnostics);
if ((object)errorResult != null)
if (errorResult is not null)
{
return TypeWithAnnotations.Create(errorResult);
}
Expand All @@ -891,23 +877,24 @@ protected NamespaceOrTypeOrAliasSymbolWithAnnotations BindNonGenericSimpleNamesp

// If we were looking up "dynamic" or "nint" at the topmost level and didn't find anything good,
// use that particular type (assuming the /langversion is supported).
if ((object)qualifierOpt == null &&
!IsViableType(result))
if (qualifierOpt is null &&
!isViableType(result))
{
if (node.Identifier.ValueText == "dynamic")
{
if ((node.Parent == null ||
node.Parent.Kind() != SyntaxKind.Attribute && // dynamic not allowed as attribute type
SyntaxFacts.IsInTypeOnlyContext(node)) &&
Compilation.LanguageVersion >= MessageID.IDS_FeatureDynamic.RequiredVersion())
if (dynamicAllowed())
{
bindingResult = Compilation.DynamicType;
ReportUseSiteDiagnosticForDynamic(diagnostics, node);
}
}
else
{
bindingResult = BindNativeIntegerSymbolIfAny(node, diagnostics);
// nint/nuint is allowed to bind to an existing namespace.
if (!isViableNamespace(result))
{
bindingResult = BindNativeIntegerSymbolIfAny(node, diagnostics);
}
}
}

Expand All @@ -919,15 +906,80 @@ protected NamespaceOrTypeOrAliasSymbolWithAnnotations BindNonGenericSimpleNamesp
if (bindingResult.Kind == SymbolKind.Alias)
{
var aliasTarget = ((AliasSymbol)bindingResult).GetAliasTarget(basesBeingResolved);
if (aliasTarget.Kind == SymbolKind.NamedType && ((NamedTypeSymbol)aliasTarget).ContainsDynamic())
if (aliasTarget is TypeSymbol type)
{
ReportUseSiteDiagnosticForDynamic(diagnostics, node);
if (type.ContainsDynamic())
{
ReportUseSiteDiagnosticForDynamic(diagnostics, node);
}

if (type.IsUnsafe())
{
ReportUnsafeIfNotAllowed(node, diagnostics);
}
}
}
}

result.Free();
return NamespaceOrTypeOrAliasSymbolWithAnnotations.CreateUnannotated(AreNullableAnnotationsEnabled(node.Identifier), bindingResult);

bool dynamicAllowed()
{
if (Compilation.LanguageVersion < MessageID.IDS_FeatureDynamic.RequiredVersion())
return false;

if (node.Parent == null)
return true;

// dynamic not allowed as attribute type
if (node.Parent.Kind() == SyntaxKind.Attribute)
return false;

if (SyntaxFacts.IsInTypeOnlyContext(node))
return true;

// using X = dynamic; is legal.
if (node.Parent is UsingDirectiveSyntax { Alias: not null })
return true;

return false;
}

static bool isViableType(LookupResult result)
{
if (!result.IsMultiViable)
return false;

foreach (var s in result.Symbols)
{
switch (s.Kind)
{
case SymbolKind.Alias:
if (((AliasSymbol)s).Target.Kind == SymbolKind.NamedType) return true;
break;
case SymbolKind.NamedType:
case SymbolKind.TypeParameter:
return true;
}
}

return false;
}

static bool isViableNamespace(LookupResult result)
{
if (!result.IsMultiViable)
return false;

foreach (var s in result.Symbols)
{
if (s.Kind == SymbolKind.Namespace)
return true;
}

return false;
}
}

/// <summary>
Expand All @@ -936,24 +988,26 @@ protected NamespaceOrTypeOrAliasSymbolWithAnnotations BindNonGenericSimpleNamesp
/// </summary>
private NamedTypeSymbol BindNativeIntegerSymbolIfAny(IdentifierNameSyntax node, BindingDiagnosticBag diagnostics)
{
SpecialType specialType;
switch (node.Identifier.Text)
{
case "nint":
specialType = SpecialType.System_IntPtr;
break;
case "nuint":
specialType = SpecialType.System_UIntPtr;
break;
default:
return null;
}
var specialType =
node.IsNint ? SpecialType.System_IntPtr :
node.IsNuint ? SpecialType.System_UIntPtr : SpecialType.None;

if (specialType == SpecialType.None)
return null;

switch (node.Parent)
{
case AttributeSyntax parent when parent.Name == node: // [nint]
return null;
case UsingDirectiveSyntax parent when parent.Name == node: // using nint; using A = nuint;
case UsingDirectiveSyntax usingDirective:
if (usingDirective.Alias != null && usingDirective.Type == node)
{
// legal to write `using A = nuint;` as long as using-alias-to-type is enabled (checked later).
break;
}

// `using nint;` not legal where 'nint' has the System.IntPtr meaning. It is legal if you were to
// have `namespace nint { }` somewhere. That is handled though in our caller.
return null;
case ArgumentSyntax parent when // nameof(nint)
(IsInsideNameof &&
Expand Down
4 changes: 2 additions & 2 deletions src/Compilers/CSharp/Portable/Binder/ImportChain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,11 @@ public Cci.IImportScope Translate(Emit.PEModuleBuilder moduleBuilder, Diagnostic
var assemblyRef = TryGetAssemblyScope(ns, moduleBuilder, diagnostics);
usedNamespaces.Add(Cci.UsedNamespaceOrType.CreateNamespace(ns.GetCciAdapter(), assemblyRef, alias));
}
else if (!target.ContainingAssembly.IsLinked)
else if (target is TypeSymbol { ContainingAssembly.IsLinked: false } typeSymbol)
{
// We skip alias imports of embedded types to avoid breaking existing code that
// imports types that can't be embedded but doesn't use them anywhere else in the code.
var typeRef = GetTypeReference((TypeSymbol)target, syntax, moduleBuilder, diagnostics);
var typeRef = GetTypeReference(typeSymbol, syntax, moduleBuilder, diagnostics);
usedNamespaces.Add(Cci.UsedNamespaceOrType.CreateType(typeRef, alias));
}
}
Expand Down
12 changes: 12 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -6847,6 +6847,18 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="WRN_DoNotCompareFunctionPointers_Title" xml:space="preserve">
<value>Do not compare function pointer values</value>
</data>
<data name="IDS_FeatureUsingTypeAlias" xml:space="preserve">
<value>using type alias</value>
</data>
<data name="ERR_BadRefInUsingAlias" xml:space="preserve">
<value>Using alias cannot be a 'ref' type.</value>
</data>
<data name="ERR_BadUnsafeInUsingDirective" xml:space="preserve">
<value>Only a using alias can be 'unsafe'.</value>
</data>
<data name="ERR_BadNullableReferenceTypeInUsingAlias" xml:space="preserve">
<value>Using alias cannot be a nullable reference type.</value>
</data>
<data name="ERR_FunctionPointerTypesInAttributeNotSupported" xml:space="preserve">
<value>Using a function pointer type in this context is not supported.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,12 @@ private static QuickAttributes GetQuickAttributes(
continue;
}

result |= QuickAttributeHelpers.GetQuickAttributes(directive.Name.GetUnqualifiedName().Identifier.ValueText, inAttribute: false);
if (directive.Name is not NameSyntax name)
{
continue;
}

result |= QuickAttributeHelpers.GetQuickAttributes(name.GetUnqualifiedName().Identifier.ValueText, inAttribute: false);
}

return result;
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2162,6 +2162,11 @@ internal enum ErrorCode
ERR_UnscopedRefAttributeInterfaceImplementation = 9102,
ERR_UnrecognizedRefSafetyRulesAttributeVersion = 9103,
ERR_BadSpecialByRefUsing = 9104,

ERR_BadRefInUsingAlias = 9105,
ERR_BadUnsafeInUsingDirective = 9106,
ERR_BadNullableReferenceTypeInUsingAlias = 9107,

#endregion

// Note: you will need to do the following after adding warnings:
Expand Down
3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2279,6 +2279,9 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code)
case ErrorCode.ERR_UnscopedRefAttributeInterfaceImplementation:
case ErrorCode.ERR_UnrecognizedRefSafetyRulesAttributeVersion:
case ErrorCode.ERR_BadSpecialByRefUsing:
case ErrorCode.ERR_BadRefInUsingAlias:
case ErrorCode.ERR_BadUnsafeInUsingDirective:
case ErrorCode.ERR_BadNullableReferenceTypeInUsingAlias:
return false;
default:
// NOTE: All error codes must be explicitly handled in this switch statement
Expand Down
3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/MessageID.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ internal enum MessageID
IDS_Missing = MessageBase + 12830,
IDS_FeatureLambdaOptionalParameters = MessageBase + 12831,
IDS_FeatureLambdaParamsArray = MessageBase + 12832,

IDS_FeatureUsingTypeAlias = MessageBase + 12833,
}

// Message IDs may refer to strings that need to be localized.
Expand Down Expand Up @@ -388,6 +390,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature)
// C# preview features.
case MessageID.IDS_FeatureLambdaOptionalParameters: // semantic check
case MessageID.IDS_FeatureLambdaParamsArray: // semantic check
case MessageID.IDS_FeatureUsingTypeAlias: // semantic check
return LanguageVersion.Preview;

// C# 11.0 features.
Expand Down
26 changes: 13 additions & 13 deletions src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit c8d3925

Please sign in to comment.