From 72508b5777f49ca22f555aa47625e936c57dea5f Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 24 Feb 2019 22:14:37 +0100 Subject: [PATCH] Add test for C# 8 nullable reference types; and fix some bugs. --- .../Helpers/RemoveCompilerAttribute.cs | 1 + .../Helpers/Tester.cs | 5 ++- .../ICSharpCode.Decompiler.Tests.csproj | 1 + .../PrettyTestRunner.cs | 6 ++++ .../TestCases/Pretty/NullableRefTypes.cs | 31 +++++++++++++++++++ .../CSharp/ExpressionBuilder.cs | 22 +++++++++---- .../CSharp/Syntax/TypeSystemAstBuilder.cs | 6 +++- .../IL/Transforms/NullPropagationTransform.cs | 2 ++ .../TypeSystem/ApplyAttributeTypeVisitor.cs | 2 ++ .../TypeSystem/ArrayType.cs | 16 ++++++++-- 10 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullableRefTypes.cs diff --git a/ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs b/ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs index 494c11225..2deaaab97 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs @@ -39,6 +39,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers "System.Runtime.CompilerServices.IsReadOnlyAttribute", "System.Runtime.CompilerServices.IsByRefLikeAttribute", "System.Runtime.CompilerServices.IsUnmanagedAttribute", + "System.Runtime.CompilerServices.NullableAttribute", "Microsoft.CodeAnalysis.EmbeddedAttribute", }; diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index aacca03dd..82b98f3db 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -245,7 +245,10 @@ namespace ICSharpCode.Decompiler.Tests.Helpers var preprocessorSymbols = GetPreprocessorSymbols(flags); if (flags.HasFlag(CompilerOptions.UseRoslyn)) { - var parseOptions = new CSharpParseOptions(preprocessorSymbols: preprocessorSymbols.ToArray(), languageVersion: Microsoft.CodeAnalysis.CSharp.LanguageVersion.Latest); + var parseOptions = new CSharpParseOptions( + preprocessorSymbols: preprocessorSymbols.ToArray(), + languageVersion: Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8 + ); var syntaxTrees = sourceFileNames.Select(f => SyntaxFactory.ParseSyntaxTree(File.ReadAllText(f), parseOptions, path: f)); var references = defaultReferences.Value; if (flags.HasFlag(CompilerOptions.ReferenceVisualBasic)) { diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index d20eaa66a..ef526d43d 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -85,6 +85,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 8846ff00d..8d747d075 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -289,6 +289,12 @@ namespace ICSharpCode.Decompiler.Tests Run(cscOptions: cscOptions); } + [Test] + public void NullableRefTypes([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) + { + RunForLibrary(cscOptions: cscOptions); + } + [Test] public void NullPropagation([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullableRefTypes.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullableRefTypes.cs new file mode 100644 index 000000000..87e073327 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullableRefTypes.cs @@ -0,0 +1,31 @@ +#nullable enable +using System.Collections.Generic; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + public class NullableRefTypes + { + private string field_string; + private string? field_nullable_string; + + private Dictionary field_generic; + private (string, string?, string) field_tuple; + private string[]?[] field_array; + private Dictionary<(string, string?), (int, string[]?, string?[])> field_complex; + + public int GetLength1(string[] arr) + { + return field_string.Length + arr.Length; + } + + public int GetLength2(string[]? arr) + { + return field_nullable_string!.Length + arr!.Length; + } + + public int? GetLength3(string[]? arr) + { + return field_nullable_string?.Length + arr?.Length; + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 3ecc72643..90254093b 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1777,11 +1777,7 @@ namespace ICSharpCode.Decompiler.CSharp .WithRR(new ResolveResult(NullableType.GetUnderlyingType(translatedTarget.Type))) .WithoutILInstruction(); } - if (translatedTarget.Type.Nullability == Nullability.Nullable) { - translatedTarget = new UnaryOperatorExpression(UnaryOperatorType.SuppressNullableWarning, translatedTarget) - .WithRR(new ResolveResult(translatedTarget.Type.ChangeNullability(Nullability.Oblivious))) - .WithoutILInstruction(); - } + translatedTarget = EnsureTargetNotNullable(translatedTarget); return translatedTarget; } } else { @@ -1790,7 +1786,20 @@ namespace ICSharpCode.Decompiler.CSharp .WithRR(new TypeResolveResult(memberDeclaringType)); } } - + + private TranslatedExpression EnsureTargetNotNullable(TranslatedExpression expr) + { + if (expr.Type.Nullability == Nullability.Nullable) { + if (expr.Expression is UnaryOperatorExpression uoe && uoe.Operator == UnaryOperatorType.NullConditional) { + return expr; + } + return new UnaryOperatorExpression(UnaryOperatorType.SuppressNullableWarning, expr) + .WithRR(new ResolveResult(expr.Type.ChangeNullability(Nullability.Oblivious))) + .WithoutILInstruction(); + } + return expr; + } + protected internal override TranslatedExpression VisitLdObj(LdObj inst, TranslationContext context) { var target = Translate(inst.Target); @@ -1875,6 +1884,7 @@ namespace ICSharpCode.Decompiler.CSharp if (arrayExpr.Type.Kind != TypeKind.Array) { arrayExpr = arrayExpr.ConvertTo(compilation.FindType(KnownTypeCode.Array), this); } + arrayExpr = EnsureTargetNotNullable(arrayExpr); if (inst.ResultType == StackType.I4) { return new MemberReferenceExpression(arrayExpr.Expression, "Length") .WithILInstruction(inst) diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 2e12ce26d..85a2e1158 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -243,7 +243,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax if (typeWithElementType is PointerType) { return ConvertType(typeWithElementType.ElementType).MakePointerType(); } else if (typeWithElementType is ArrayType) { - return ConvertType(typeWithElementType.ElementType).MakeArrayType(((ArrayType)type).Dimensions); + var astType = ConvertType(typeWithElementType.ElementType).MakeArrayType(((ArrayType)type).Dimensions); + if (type.Nullability == Nullability.Nullable) + return astType.MakeNullableType(); + else + return astType; } else if (typeWithElementType is ByReferenceType) { return ConvertType(typeWithElementType.ElementType).MakeRefType(); } else { diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs index 979fb1307..f65415587 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs @@ -210,6 +210,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms // ensure the access chain does not contain any 'nullable.unwrap' that aren't directly part of the chain if (ArgumentsAfterFirstMayUnwrapNull(call.Arguments)) return false; + } else if (inst is LdLen ldLen) { + inst = ldLen.Array; } else if (inst is NullableUnwrap unwrap) { inst = unwrap.Argument; } else if (inst is DynamicGetMemberInstruction dynGetMember) { diff --git a/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs b/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs index d6457ac04..a41841a0e 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs @@ -186,11 +186,13 @@ namespace ICSharpCode.Decompiler.TypeSystem int normalArgCount = Math.Min(type.TypeArguments.Count, TupleType.RestPosition - 1); for (int i = 0; i < normalArgCount; i++) { dynamicTypeIndex++; + nullabilityTypeIndex++; elementTypes.Add(type.TypeArguments[i].AcceptVisitor(this)); } if (type.TypeArguments.Count == TupleType.RestPosition) { type = type.TypeArguments.Last() as ParameterizedType; dynamicTypeIndex++; + nullabilityTypeIndex++; if (type != null && TupleType.IsTupleCompatible(type, out int nestedCardinality)) { tupleTypeIndex += nestedCardinality; } else { diff --git a/ICSharpCode.Decompiler/TypeSystem/ArrayType.cs b/ICSharpCode.Decompiler/TypeSystem/ArrayType.cs index f804fb377..55f794138 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ArrayType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ArrayType.cs @@ -89,7 +89,19 @@ namespace ICSharpCode.Decompiler.TypeSystem ArrayType a = other as ArrayType; return a != null && elementType.Equals(a.elementType) && a.dimensions == dimensions && a.nullability == nullability; } - + + public override string ToString() + { + switch (nullability) { + case Nullability.Nullable: + return elementType.ToString() + NameSuffix + "?"; + case Nullability.NotNullable: + return elementType.ToString() + NameSuffix + "!"; + default: + return elementType.ToString() + NameSuffix; + } + } + public override IEnumerable DirectBaseTypes { get { List baseTypes = new List(); @@ -125,7 +137,7 @@ namespace ICSharpCode.Decompiler.TypeSystem else return compilation.FindType(KnownTypeCode.Array).GetMethods(typeArguments, filter, options); } - + public override IEnumerable GetAccessors(Predicate filter = null, GetMemberOptions options = GetMemberOptions.None) { if ((options & GetMemberOptions.IgnoreInheritedMembers) == GetMemberOptions.IgnoreInheritedMembers)