From 06cf9c1747b4474c5b40e83d06db3c734a894628 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 16 Feb 2019 22:26:30 +0100 Subject: [PATCH] Avoid exceptions on IType->ArrayType or IType->ITypeParameter casts due to NullabilityAnnotatedType decorator. --- .../CSharp/Syntax/TypeSystemAstBuilder.cs | 2 +- .../TypeSystem/ArrayType.cs | 16 ++++-- .../TypeSystem/ITypeParameter.cs | 3 + .../Implementation/AbstractTypeParameter.cs | 6 +- .../NullabilityAnnotatedType.cs | 56 ++++++++++++++++--- .../TypeSystem/NormalizeTypeVisitor.cs | 10 +++- 6 files changed, 75 insertions(+), 18 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 8a96e3676..2dbb0abc3 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -252,7 +252,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return ConvertTypeHelper(pt.GenericType, pt.TypeArguments); } if (type is NullabilityAnnotatedType nat) { - var astType = ConvertType(nat.ElementType); + var astType = ConvertType(nat.TypeWithoutAnnotation); if (nat.Nullability == Nullability.Nullable) astType = astType.MakeNullableType(); return astType; diff --git a/ICSharpCode.Decompiler/TypeSystem/ArrayType.cs b/ICSharpCode.Decompiler/TypeSystem/ArrayType.cs index 8926b92f7..f804fb377 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ArrayType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ArrayType.cs @@ -30,8 +30,9 @@ namespace ICSharpCode.Decompiler.TypeSystem { readonly int dimensions; readonly ICompilation compilation; - - public ArrayType(ICompilation compilation, IType elementType, int dimensions = 1) : base(elementType) + readonly Nullability nullability; + + public ArrayType(ICompilation compilation, IType elementType, int dimensions = 1, Nullability nullability = Nullability.Oblivious) : base(elementType) { if (compilation == null) throw new ArgumentNullException("compilation"); @@ -39,6 +40,7 @@ namespace ICSharpCode.Decompiler.TypeSystem throw new ArgumentOutOfRangeException("dimensions", dimensions, "dimensions must be positive"); this.compilation = compilation; this.dimensions = dimensions; + this.nullability = nullability; ICompilationProvider p = elementType as ICompilationProvider; if (p != null && p.Compilation != compilation) @@ -57,12 +59,14 @@ namespace ICSharpCode.Decompiler.TypeSystem get { return dimensions; } } + public override Nullability Nullability => nullability; + public override IType ChangeNullability(Nullability nullability) { - if (nullability == Nullability.Oblivious) + if (nullability == this.nullability) return this; else - return new NullabilityAnnotatedType(this, nullability); + return new ArrayType(compilation, elementType, dimensions, nullability); } public override string NameSuffix { @@ -83,7 +87,7 @@ namespace ICSharpCode.Decompiler.TypeSystem public override bool Equals(IType other) { ArrayType a = other as ArrayType; - return a != null && elementType.Equals(a.elementType) && a.dimensions == dimensions; + return a != null && elementType.Equals(a.elementType) && a.dimensions == dimensions && a.nullability == nullability; } public override IEnumerable DirectBaseTypes { @@ -152,7 +156,7 @@ namespace ICSharpCode.Decompiler.TypeSystem if (e == elementType) return this; else - return new ArrayType(compilation, e, dimensions); + return new ArrayType(compilation, e, dimensions, nullability); } } diff --git a/ICSharpCode.Decompiler/TypeSystem/ITypeParameter.cs b/ICSharpCode.Decompiler/TypeSystem/ITypeParameter.cs index 7897a2375..27a5c9cc4 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ITypeParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ITypeParameter.cs @@ -89,6 +89,9 @@ namespace ICSharpCode.Decompiler.TypeSystem /// /// Nullability of the reference type constraint. (e.g. "where T : class?"). + /// + /// Note that the nullability of a use of the type parameter may differ from this. + /// E.g. "T? GetNull<T>() where T : class => null;" /// Nullability NullabilityConstraint { get; } } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractTypeParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractTypeParameter.cs index 25e8df0a8..632dee0d2 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractTypeParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractTypeParameter.cs @@ -193,14 +193,14 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation } bool IType.IsByRefLike => false; - public Nullability Nullability => Nullability.Oblivious; + Nullability IType.Nullability => NullabilityConstraint; public IType ChangeNullability(Nullability nullability) { - if (nullability == Nullability.Oblivious) + if (nullability == NullabilityConstraint) return this; else - return new NullabilityAnnotatedType(this, nullability); + return new NullabilityAnnotatedTypeParameter(this, nullability); } IType IType.DeclaringType { diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/NullabilityAnnotatedType.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/NullabilityAnnotatedType.cs index b2b6cb460..4ea5d8eb3 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/NullabilityAnnotatedType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/NullabilityAnnotatedType.cs @@ -1,22 +1,30 @@ -using System.Diagnostics; +using System.Collections.Generic; +using System.Diagnostics; namespace ICSharpCode.Decompiler.TypeSystem.Implementation { - public sealed class NullabilityAnnotatedType : DecoratedType, IType + /// + /// A decorator that annotates the nullability status for a type. + /// Note: ArrayType does not use a decorator, but has direct support for nullability. + /// + public class NullabilityAnnotatedType : DecoratedType, IType { readonly Nullability nullability; internal NullabilityAnnotatedType(IType type, Nullability nullability) : base(type) { - Debug.Assert(nullability != Nullability.Oblivious); - Debug.Assert(!(type is ParameterizedType || type is NullabilityAnnotatedType)); + Debug.Assert(nullability != type.Nullability); + // Due to IType -> concrete type casts all over the type system, we can insert + // the NullabilityAnnotatedType wrapper only in some limited places. + Debug.Assert(type is ITypeDefinition + || (type is ITypeParameter && this is ITypeParameter)); this.nullability = nullability; } public Nullability Nullability => nullability; - public IType ElementType => baseType; + public IType TypeWithoutAnnotation => baseType; public override IType AcceptVisitor(TypeVisitor visitor) { @@ -42,14 +50,48 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation { IType newBase = baseType.AcceptVisitor(visitor); if (newBase != baseType) - return new NullabilityAnnotatedType(newBase, nullability); + return newBase.ChangeNullability(nullability); else return this; } public override string ToString() { - return baseType.ToString() + (nullability == Nullability.Nullable ? "?" : "!"); + switch (nullability) { + case Nullability.Nullable: + return $"{baseType.ToString()}?"; + case Nullability.NotNullable: + return $"{baseType.ToString()}!"; + default: + Debug.Assert(nullability == Nullability.Oblivious); + return $"{baseType.ToString()}~"; + } } } + + public sealed class NullabilityAnnotatedTypeParameter : NullabilityAnnotatedType, ITypeParameter + { + readonly new ITypeParameter baseType; + + internal NullabilityAnnotatedTypeParameter(ITypeParameter type, Nullability nullability) + : base(type, nullability) + { + this.baseType = type; + } + + SymbolKind ITypeParameter.OwnerType => baseType.OwnerType; + IEntity ITypeParameter.Owner => baseType.Owner; + int ITypeParameter.Index => baseType.Index; + string ITypeParameter.Name => baseType.Name; + string ISymbol.Name => baseType.Name; + VarianceModifier ITypeParameter.Variance => baseType.Variance; + IType ITypeParameter.EffectiveBaseClass => baseType.EffectiveBaseClass; + IReadOnlyCollection ITypeParameter.EffectiveInterfaceSet => baseType.EffectiveInterfaceSet; + bool ITypeParameter.HasDefaultConstructorConstraint => baseType.HasDefaultConstructorConstraint; + bool ITypeParameter.HasReferenceTypeConstraint => baseType.HasReferenceTypeConstraint; + bool ITypeParameter.HasValueTypeConstraint => baseType.HasValueTypeConstraint; + Nullability ITypeParameter.NullabilityConstraint => baseType.NullabilityConstraint; + SymbolKind ISymbol.SymbolKind => SymbolKind.TypeParameter; + IEnumerable ITypeParameter.GetAttributes() => baseType.GetAttributes(); + } } diff --git a/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs b/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs index 5bda7f79e..203e1e971 100644 --- a/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs +++ b/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs @@ -72,11 +72,19 @@ namespace ICSharpCode.Decompiler.TypeSystem public override IType VisitNullabilityAnnotatedType(NullabilityAnnotatedType type) { if (RemoveNullability) - return type.ElementType.AcceptVisitor(this); + return base.VisitNullabilityAnnotatedType(type).ChangeNullability(Nullability.Oblivious); else return base.VisitNullabilityAnnotatedType(type); } + public override IType VisitArrayType(ArrayType type) + { + if (RemoveNullability) + return base.VisitArrayType(type).ChangeNullability(Nullability.Oblivious); + else + return base.VisitArrayType(type); + } + public override IType VisitModOpt(ModifiedType type) { if (RemoveModOpt) {