-
Notifications
You must be signed in to change notification settings - Fork 470
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add lightup support for nullable reference types
- Loading branch information
Showing
8 changed files
with
447 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using Microsoft.CodeAnalysis; | ||
|
||
namespace Analyzer.Utilities.Lightup | ||
{ | ||
internal static class ITypeSymbolExtensions | ||
{ | ||
private static readonly Func<ITypeSymbol, NullableAnnotation> s_nullableAnnotation | ||
= LightupHelpers.CreateSymbolPropertyAccessor<ITypeSymbol, NullableAnnotation>(typeof(ITypeSymbol), nameof(NullableAnnotation)); | ||
private static readonly Func<ITypeSymbol, NullableAnnotation, ITypeSymbol> s_withNullableAnnotation | ||
= LightupHelpers.CreateSymbolWithPropertyAccessor<ITypeSymbol, NullableAnnotation>(typeof(ITypeSymbol), nameof(NullableAnnotation)); | ||
|
||
public static NullableAnnotation NullableAnnotation(this ITypeSymbol typeSymbol) | ||
=> s_nullableAnnotation(typeSymbol); | ||
|
||
public static ITypeSymbol WithNullableAnnotation(this ITypeSymbol typeSymbol, NullableAnnotation nullableAnnotation) | ||
=> s_withNullableAnnotation(typeSymbol, nullableAnnotation); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Linq; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
using Microsoft.CodeAnalysis; | ||
|
||
namespace Analyzer.Utilities.Lightup | ||
{ | ||
internal static class LightupHelpers | ||
{ | ||
internal static Func<TSyntax, TProperty> CreateSyntaxPropertyAccessor<TSyntax, TProperty>(Type type, string propertyName) | ||
where TSyntax : SyntaxNode | ||
=> CreatePropertyAccessor<TSyntax, TProperty>(type, "syntax", propertyName); | ||
|
||
internal static Func<TSymbol, TProperty> CreateSymbolPropertyAccessor<TSymbol, TProperty>(Type type, string propertyName) | ||
where TSymbol : ISymbol | ||
=> CreatePropertyAccessor<TSymbol, TProperty>(type, "symbol", propertyName); | ||
|
||
private static Func<T, TProperty> CreatePropertyAccessor<T, TProperty>(Type type, string parameterName, string propertyName) | ||
{ | ||
static TProperty FallbackAccessor(T instance) | ||
{ | ||
if (instance == null) | ||
{ | ||
// Unlike an extension method which would throw ArgumentNullException here, the light-up | ||
// behavior needs to match behavior of the underlying property. | ||
throw new NullReferenceException(); | ||
} | ||
|
||
return default!; | ||
} | ||
|
||
if (type == null) | ||
{ | ||
return FallbackAccessor; | ||
} | ||
|
||
if (!typeof(T).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) | ||
{ | ||
throw new InvalidOperationException(); | ||
} | ||
|
||
var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); | ||
if (property == null) | ||
{ | ||
return FallbackAccessor; | ||
} | ||
|
||
if (!typeof(TProperty).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo())) | ||
{ | ||
if (property.PropertyType.GetTypeInfo().IsEnum | ||
&& typeof(TProperty).GetTypeInfo().IsEnum | ||
&& Enum.GetUnderlyingType(typeof(TProperty)).GetTypeInfo().IsAssignableFrom(Enum.GetUnderlyingType(property.PropertyType).GetTypeInfo())) | ||
{ | ||
// Allow this | ||
} | ||
else | ||
{ | ||
throw new InvalidOperationException(); | ||
} | ||
} | ||
|
||
var parameter = Expression.Parameter(typeof(T), parameterName); | ||
Expression instance = | ||
type.GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()) | ||
? (Expression)parameter | ||
: Expression.Convert(parameter, type); | ||
|
||
Expression result = Expression.Call(instance, property.GetMethod); | ||
if (!typeof(TProperty).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo())) | ||
{ | ||
result = Expression.Convert(result, typeof(TProperty)); | ||
} | ||
|
||
Expression<Func<T, TProperty>> expression = Expression.Lambda<Func<T, TProperty>>(result, parameter); | ||
return expression.Compile(); | ||
} | ||
|
||
internal static Func<TSyntax, TProperty, TSyntax> CreateSyntaxWithPropertyAccessor<TSyntax, TProperty>(Type type, string propertyName) | ||
where TSyntax : SyntaxNode | ||
=> CreateWithPropertyAccessor<TSyntax, TProperty>(type, "syntax", propertyName); | ||
|
||
internal static Func<TSymbol, TProperty, TSymbol> CreateSymbolWithPropertyAccessor<TSymbol, TProperty>(Type type, string propertyName) | ||
where TSymbol : ISymbol | ||
=> CreateWithPropertyAccessor<TSymbol, TProperty>(type, "symbol", propertyName); | ||
|
||
private static Func<T, TProperty, T> CreateWithPropertyAccessor<T, TProperty>(Type type, string parameterName, string propertyName) | ||
{ | ||
static T FallbackAccessor(T instance, TProperty newValue) | ||
{ | ||
if (instance == null) | ||
{ | ||
// Unlike an extension method which would throw ArgumentNullException here, the light-up | ||
// behavior needs to match behavior of the underlying property. | ||
throw new NullReferenceException(); | ||
} | ||
|
||
if (Equals(newValue, default(TProperty))) | ||
{ | ||
return instance; | ||
} | ||
|
||
throw new NotSupportedException(); | ||
} | ||
|
||
if (type == null) | ||
{ | ||
return FallbackAccessor; | ||
} | ||
|
||
if (!typeof(T).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) | ||
{ | ||
throw new InvalidOperationException(); | ||
} | ||
|
||
var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); | ||
if (property == null) | ||
{ | ||
return FallbackAccessor; | ||
} | ||
|
||
if (!typeof(TProperty).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo())) | ||
{ | ||
if (property.PropertyType.GetTypeInfo().IsEnum | ||
&& typeof(TProperty).GetTypeInfo().IsEnum | ||
&& Enum.GetUnderlyingType(typeof(TProperty)).GetTypeInfo().IsAssignableFrom(Enum.GetUnderlyingType(property.PropertyType).GetTypeInfo())) | ||
{ | ||
// Allow this | ||
} | ||
else | ||
{ | ||
throw new InvalidOperationException(); | ||
} | ||
} | ||
|
||
var methodInfo = type.GetTypeInfo().GetDeclaredMethods("With" + propertyName) | ||
.SingleOrDefault(m => !m.IsStatic && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.Equals(property.PropertyType)); | ||
if (methodInfo is null) | ||
{ | ||
return FallbackAccessor; | ||
} | ||
|
||
var parameter = Expression.Parameter(typeof(T), parameterName); | ||
var valueParameter = Expression.Parameter(typeof(TProperty), methodInfo.GetParameters()[0].Name); | ||
Expression instance = | ||
type.GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()) | ||
? (Expression)parameter | ||
: Expression.Convert(parameter, type); | ||
Expression value = | ||
property.PropertyType.GetTypeInfo().IsAssignableFrom(typeof(TProperty).GetTypeInfo()) | ||
? (Expression)valueParameter | ||
: Expression.Convert(valueParameter, property.PropertyType); | ||
|
||
Expression<Func<T, TProperty, T>> expression = | ||
Expression.Lambda<Func<T, TProperty, T>>( | ||
Expression.Call(instance, methodInfo, value), | ||
parameter, | ||
valueParameter); | ||
return expression.Compile(); | ||
} | ||
|
||
internal static Func<T, TArg, TValue> CreateAccessorWithArgument<T, TArg, TValue>(Type type, string parameterName, Type argumentType, string argumentName, string methodName) | ||
{ | ||
static TValue FallbackAccessor(T instance, TArg argument) | ||
{ | ||
if (instance == null) | ||
{ | ||
// Unlike an extension method which would throw ArgumentNullException here, the light-up | ||
// behavior needs to match behavior of the underlying property. | ||
throw new NullReferenceException(); | ||
} | ||
|
||
return default!; | ||
} | ||
|
||
if (type == null) | ||
{ | ||
return FallbackAccessor; | ||
} | ||
|
||
if (!typeof(T).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) | ||
{ | ||
throw new InvalidOperationException(); | ||
} | ||
|
||
var method = type.GetTypeInfo().GetDeclaredMethod(methodName); | ||
if (method == null) | ||
{ | ||
return FallbackAccessor; | ||
} | ||
|
||
if (!typeof(TValue).GetTypeInfo().IsAssignableFrom(method.ReturnType.GetTypeInfo())) | ||
{ | ||
if (method.ReturnType.GetTypeInfo().IsEnum | ||
&& typeof(TValue).GetTypeInfo().IsEnum | ||
&& Enum.GetUnderlyingType(typeof(TValue)).GetTypeInfo().IsAssignableFrom(Enum.GetUnderlyingType(method.ReturnType).GetTypeInfo())) | ||
{ | ||
// Allow this | ||
} | ||
else | ||
{ | ||
throw new InvalidOperationException(); | ||
} | ||
} | ||
|
||
var parameter = Expression.Parameter(typeof(T), parameterName); | ||
var argument = Expression.Parameter(typeof(TArg), argumentName); | ||
Expression instance = | ||
type.GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()) | ||
? (Expression)parameter | ||
: Expression.Convert(parameter, type); | ||
Expression convertedArgument = | ||
argumentType.GetTypeInfo().IsAssignableFrom(typeof(TArg).GetTypeInfo()) | ||
? (Expression)argument | ||
: Expression.Convert(argument, type); | ||
|
||
Expression result = Expression.Call(instance, method, convertedArgument); | ||
if (!typeof(TValue).GetTypeInfo().IsAssignableFrom(method.ReturnType.GetTypeInfo())) | ||
{ | ||
result = Expression.Convert(result, typeof(TValue)); | ||
} | ||
|
||
Expression<Func<T, TArg, TValue>> expression = Expression.Lambda<Func<T, TArg, TValue>>(result, parameter, argument); | ||
return expression.Compile(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System.Diagnostics.CodeAnalysis; | ||
|
||
namespace Analyzer.Utilities.Lightup | ||
{ | ||
/// <summary> | ||
/// Represents the nullability of values that can be assigned | ||
/// to an expression used as an lvalue. | ||
/// </summary> | ||
[SuppressMessage("Design", "CA1028:Enum Storage should be Int32", Justification = "Underlying type must match the original underlying type.")] | ||
internal enum NullableAnnotation : byte | ||
{ | ||
/// <summary> | ||
/// The expression has not been analyzed, or the syntax is | ||
/// not an expression (such as a statement). | ||
/// </summary> | ||
/// <remarks> | ||
/// There are a few different reasons the expression could | ||
/// have not been analyzed: | ||
/// 1) The symbol producing the expression comes from | ||
/// a method that has not been annotated, such as | ||
/// invoking a C# 7.3 or earlier method, or a | ||
/// method in this compilation that is in a disabled | ||
/// context. | ||
/// 2) Nullable is completely disabled in this | ||
/// compilation. | ||
/// </remarks> | ||
None = 0, | ||
|
||
/// <summary> | ||
/// The expression is not annotated (does not have a ?). | ||
/// </summary> | ||
NotAnnotated, | ||
|
||
/// <summary> | ||
/// The expression is annotated (does have a ?). | ||
/// </summary> | ||
Annotated, | ||
} | ||
} |
Oops, something went wrong.