Skip to content

Commit

Permalink
Allow to forward obsolete types without any diagnostics.
Browse files Browse the repository at this point in the history
Closes dotnet#61264.
  • Loading branch information
AlekseyTs committed Jun 24, 2022
1 parent 69bdfb2 commit 0494bbb
Show file tree
Hide file tree
Showing 4 changed files with 531 additions and 38 deletions.
92 changes: 54 additions & 38 deletions src/Compilers/CSharp/Portable/Symbols/ObsoleteAttributeHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,51 +130,67 @@ internal static ObsoleteDiagnosticKind GetObsoleteDiagnosticKind(Symbol symbol,
/// </summary>
internal static DiagnosticInfo CreateObsoleteDiagnostic(Symbol symbol, BinderFlags location)
{
var data = symbol.ObsoleteAttributeData;
Debug.Assert(data != null);
DiagnosticInfo result = createObsoleteDiagnostic(symbol, location);
Debug.Assert(result?.IsObsoleteDiagnostic() != false);
return result;

if (data == null)
static DiagnosticInfo createObsoleteDiagnostic(Symbol symbol, BinderFlags location)
{
return null;
}
var data = symbol.ObsoleteAttributeData;
Debug.Assert(data != null);

// At this point, we are going to issue diagnostics and therefore the data shouldn't be
// uninitialized.
Debug.Assert(!data.IsUninitialized);
if (data == null)
{
return null;
}

// The native compiler suppresses Obsolete diagnostics in these locations.
if (location.Includes(BinderFlags.SuppressObsoleteChecks))
{
return null;
}
// At this point, we are going to issue diagnostics and therefore the data shouldn't be
// uninitialized.
Debug.Assert(!data.IsUninitialized);

if (data.Kind == ObsoleteAttributeKind.Experimental)
{
Debug.Assert(data.Message == null);
Debug.Assert(!data.IsError);
// Provide an explicit format for fully-qualified type names.
return new CSDiagnosticInfo(ErrorCode.WRN_Experimental, new FormattedSymbol(symbol, SymbolDisplayFormat.CSharpErrorMessageFormat));
// The native compiler suppresses Obsolete diagnostics in these locations.
if (location.Includes(BinderFlags.SuppressObsoleteChecks))
{
return null;
}

if (data.Kind == ObsoleteAttributeKind.Experimental)
{
Debug.Assert(data.Message == null);
Debug.Assert(!data.IsError);
// Provide an explicit format for fully-qualified type names.
return new CSDiagnosticInfo(ErrorCode.WRN_Experimental, new FormattedSymbol(symbol, SymbolDisplayFormat.CSharpErrorMessageFormat));
}

// Issue a specialized diagnostic for add methods of collection initializers
var isColInit = location.Includes(BinderFlags.CollectionInitializerAddMethod);
Debug.Assert(!isColInit || symbol.Name == WellKnownMemberNames.CollectionInitializerAddMethodName);
var errorCode = (message: data.Message, isError: data.IsError, isColInit) switch
{
// dev11 had a bug in this area (i.e. always produce a warning when there's no message) and we have to match it.
(message: null, isError: _, isColInit: true) => ErrorCode.WRN_DeprecatedCollectionInitAdd,
(message: null, isError: _, isColInit: false) => ErrorCode.WRN_DeprecatedSymbol,
(message: { }, isError: true, isColInit: true) => ErrorCode.ERR_DeprecatedCollectionInitAddStr,
(message: { }, isError: true, isColInit: false) => ErrorCode.ERR_DeprecatedSymbolStr,
(message: { }, isError: false, isColInit: true) => ErrorCode.WRN_DeprecatedCollectionInitAddStr,
(message: { }, isError: false, isColInit: false) => ErrorCode.WRN_DeprecatedSymbolStr
};

var arguments = data.Message is string message
? new object[] { symbol, message }
: new object[] { symbol };

return new CustomObsoleteDiagnosticInfo(MessageProvider.Instance, (int)errorCode, data, arguments);
}
}

// Issue a specialized diagnostic for add methods of collection initializers
var isColInit = location.Includes(BinderFlags.CollectionInitializerAddMethod);
Debug.Assert(!isColInit || symbol.Name == WellKnownMemberNames.CollectionInitializerAddMethodName);
var errorCode = (message: data.Message, isError: data.IsError, isColInit) switch
{
// dev11 had a bug in this area (i.e. always produce a warning when there's no message) and we have to match it.
(message: null, isError: _, isColInit: true) => ErrorCode.WRN_DeprecatedCollectionInitAdd,
(message: null, isError: _, isColInit: false) => ErrorCode.WRN_DeprecatedSymbol,
(message: { }, isError: true, isColInit: true) => ErrorCode.ERR_DeprecatedCollectionInitAddStr,
(message: { }, isError: true, isColInit: false) => ErrorCode.ERR_DeprecatedSymbolStr,
(message: { }, isError: false, isColInit: true) => ErrorCode.WRN_DeprecatedCollectionInitAddStr,
(message: { }, isError: false, isColInit: false) => ErrorCode.WRN_DeprecatedSymbolStr
};

var arguments = data.Message is string message
? new object[] { symbol, message }
: new object[] { symbol };

return new CustomObsoleteDiagnosticInfo(MessageProvider.Instance, (int)errorCode, data, arguments);
internal static bool IsObsoleteDiagnostic(this DiagnosticInfo diagnosticInfo)
{
return (ErrorCode)diagnosticInfo.Code is
(ErrorCode.WRN_Experimental or ErrorCode.WRN_DeprecatedCollectionInitAdd or
ErrorCode.WRN_DeprecatedSymbol or ErrorCode.ERR_DeprecatedCollectionInitAddStr or
ErrorCode.ERR_DeprecatedSymbolStr or ErrorCode.WRN_DeprecatedCollectionInitAddStr or
ErrorCode.WRN_DeprecatedSymbolStr);
}
}
}
95 changes: 95 additions & 0 deletions src/Compilers/CSharp/Portable/Symbols/Symbol_Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -388,8 +388,12 @@ internal bool LoadAndValidateAttributes(
this.PostEarlyDecodeWellKnownAttributeTypes();
}

Debug.Assert(!earlyDecodingOnly);

this.PostDecodeWellKnownAttributes(boundAttributes, attributesToBind, diagnostics, symbolPart, wellKnownAttributeData);

removeObsoleteDiagnosticsForForwardeTypes(boundAttributes, attributesToBind, ref diagnostics);

// Store attributes into the bag.
bool lazyAttributesStoredOnThisThread = false;
if (lazyCustomAttributesBag.SetAttributes(boundAttributes))
Expand Down Expand Up @@ -418,6 +422,97 @@ internal bool LoadAndValidateAttributes(
Debug.Assert(lazyCustomAttributesBag.IsSealed);
diagnostics.Free();
return lazyAttributesStoredOnThisThread;

void removeObsoleteDiagnosticsForForwardeTypes(ImmutableArray<CSharpAttributeData> boundAttributes, ImmutableArray<AttributeSyntax> attributesToBind, ref BindingDiagnosticBag diagnostics)
{
if (!boundAttributes.IsDefaultOrEmpty &&
this is SourceAssemblySymbol &&
!diagnostics.DiagnosticBag!.IsEmptyWithoutResolution &&
diagnostics.DiagnosticBag.AsEnumerableWithoutResolution().OfType<DiagnosticWithInfo>().Where(isObsoleteDiagnostics).Any())
{
// We are binding attributes for an assembly and have an obsolete diagnostic reported,
// or we have lazy diagnostic, that might be resolved to an obsolete diagnostic later.
// We would like to filter out a diagnostic like that for a forwarded type.
// The TypeForwardedTo attribute takes only one argument, which must be System.Type and it
// designates the forwarded type. The only form of System.Type value accepted
// as an argument for an attribute is a 'typeof' expression. The only obsolete diagnostics
// that can be reported for a 'typeof' expression, is diagnostics for its argument, which is
// the reference to a type. A forwarded type, when we are dealing with a TypeForwardedTo
// application.

// The general strategy:
// 1. Collect locations of the first argument of each TypeForwardedTo attribute application.
// 2. Collect obsolete diagnostics reported withing the span of those locations.
// 3. Remove the collected diagnostics, if any.


var builder = ArrayBuilder<Location>.GetInstance();
int totalAttributesCount = attributesToBind.Length;

// 1. Collect locations of the first argument of each TypeForwardedTo attribute application.
for (int i = 0; i < totalAttributesCount; i++)
{
CSharpAttributeData boundAttribute = boundAttributes[i];

if (!boundAttribute.HasErrors && boundAttribute.IsTargetAttribute(this, AttributeDescription.TypeForwardedToAttribute) &&
boundAttribute.CommonConstructorArguments[0].ValueInternal is TypeSymbol &&
attributesToBind[i].ArgumentList?.Arguments[0].Expression.Location is { } location)
{
builder.Add(location);
}
}

if (builder.Count != 0)
{
var toRemove = new HashSet<Diagnostic>(ReferenceEqualityComparer.Instance);

// 2. Collect obsolete diagnostics reported withing the span of those locations.
foreach (Diagnostic d in diagnostics.DiagnosticBag.AsEnumerableWithoutResolution())
{
if (d is DiagnosticWithInfo withInfo && isObsoleteDiagnostics(withInfo))
{
Location location = withInfo.Location;

foreach (Location argumentLocation in builder)
{
if (location.SourceTree == argumentLocation.SourceTree &&
argumentLocation.SourceSpan.Contains(location.SourceSpan))
{
toRemove.Add(withInfo);
break;
}
}
}
}

// 3. Remove the collected diagnostics, if any.
if (toRemove.Count != 0)
{
var filtered = BindingDiagnosticBag.GetInstance();

filtered.AddDependencies(diagnostics);

foreach (Diagnostic d in diagnostics.DiagnosticBag.AsEnumerableWithoutResolution())
{
if (!toRemove.Contains(d))
{
filtered.Add(d);
}
}

diagnostics.Free();
diagnostics = filtered;
}
}

builder.Free();
}
}

static bool isObsoleteDiagnostics(DiagnosticWithInfo d)
{
return d.HasLazyInfo ? d.LazyInfo is LazyObsoleteDiagnosticInfo : d.Info.IsObsoleteDiagnostic();
}
}
#nullable disable

Expand Down
Loading

0 comments on commit 0494bbb

Please sign in to comment.