From 2e2cbb6c3b998f2078f8d30e0111289234415e86 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 11 Jan 2018 13:00:37 +0100 Subject: [PATCH 1/8] Add HasUnconstraintGenericParameter check to suppress reporting of diagnostic. --- .../UseIsNullCheck/UseIsNullCheckTests.cs | 50 +++++++++++++++++++ .../AbstractUseIsNullDiagnosticAnalyzer.cs | 25 +++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs b/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs index d5872311cafd7..7b05727d9fc39 100644 --- a/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs +++ b/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs @@ -217,5 +217,55 @@ void M(string s1, string s2) } }"); } + + [WorkItem(23581, "https://github.com/dotnet/roslyn/issues/23581")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)] + public async Task TestMissingIfValueParameterTypeIsUnconstraintGeneric() + { + await TestMissingAsync( +@" +class C +{ + public static void NotNull(T value, string parameterName) + { + if ([||]ReferenceEquals(value, null)) + { + throw new System.ArgumentNullException(parameterName); + } + } +} +"); + } + + [WorkItem(23581, "https://github.com/dotnet/roslyn/issues/23581")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)] + public async Task TestValueParameterTypeIsConstraintGeneric() + { + await TestInRegularAndScriptAsync( +@" +class C +{ + public static void NotNull(T value, string parameterName) where T:class + { + if ([||]ReferenceEquals(value, null)) + { + throw new System.ArgumentNullException(parameterName); + } + } +} +", +@" +class C +{ + public static void NotNull(T value, string parameterName) where T:class + { + if (value is null) + { + throw new System.ArgumentNullException(parameterName); + } + } +} +"); + } } } diff --git a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs index c12b69441a86a..e0464a9faefeb 100644 --- a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs @@ -2,6 +2,7 @@ using System.Collections.Immutable; using System.Linq; +using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageServices; @@ -49,7 +50,7 @@ protected override void InitializeWorker(AnalysisContext context) private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, IMethodSymbol referenceEqualsMethod) { var cancellationToken = context.CancellationToken; - + var semanticModel = context.SemanticModel; var syntaxTree = semanticModel.SyntaxTree; if (!IsLanguageVersionSupported(syntaxTree.Options)) @@ -108,6 +109,10 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, IMethodSymbol refe return; } + if (HasUnconstraintGenericParameter(syntaxFacts, semanticModel, arguments[0], arguments[1], cancellationToken)) + { + return; + } var additionalLocations = ImmutableArray.Create(invocation.GetLocation()); var properties = ImmutableDictionary.Empty; @@ -125,7 +130,23 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, IMethodSymbol refe additionalLocations, properties)); } - private bool MatchesPattern(ISyntaxFactsService syntaxFacts, SyntaxNode node1, SyntaxNode node2) + private static bool HasUnconstraintGenericParameter(ISyntaxFactsService syntaxFacts, SemanticModel semanticModel, SyntaxNode node1, SyntaxNode node2, CancellationToken cancellationToken) + { + var valueNode = syntaxFacts.IsNullLiteralExpression(node1) ? node2 : node1; + var argumentExpression = syntaxFacts.GetExpressionOfArgument(valueNode); + if (argumentExpression != null) + { + var parameterType = semanticModel.GetTypeInfo(argumentExpression, cancellationToken).Type; + if (parameterType is ITypeParameterSymbol typeParameter) + { + return !typeParameter.HasReferenceTypeConstraint; + } + } + + return false; + } + + private static bool MatchesPattern(ISyntaxFactsService syntaxFacts, SyntaxNode node1, SyntaxNode node2) => syntaxFacts.IsNullLiteralExpression(syntaxFacts.GetExpressionOfArgument(node1)) && !syntaxFacts.IsNullLiteralExpression(syntaxFacts.GetExpressionOfArgument(node2)); } From f67fb25978eebcff0f355d6f612ea4d7e2189c7b Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Mon, 15 Jan 2018 10:19:13 +0100 Subject: [PATCH 2/8] Removed generic constraint check. Changed C# null check from (v is null) to (v == null). --- .../UseIsNullCheck/UseIsNullCheckTests.cs | 33 ++++++++++++------- .../CSharpUseIsNullCheckCodeFixProvider.cs | 12 ++++--- .../AbstractUseIsNullDiagnosticAnalyzer.cs | 21 ------------ 3 files changed, 30 insertions(+), 36 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs b/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs index 7b05727d9fc39..65ce66c37e560 100644 --- a/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs +++ b/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs @@ -36,7 +36,7 @@ class C { void M(string s) { - if (s is null) + if (s == null) return; } }"); @@ -62,7 +62,7 @@ class C { void M(string s) { - if (s is null) + if (s == null) return; } }"); @@ -88,7 +88,7 @@ class C { void M(string s) { - if (s is null) + if (s == null) return; } }"); @@ -114,7 +114,7 @@ class C { void M(string s) { - if (s is null) + if (s == null) return; } }"); @@ -140,7 +140,7 @@ class C { void M(string s) { - if (!(s is null)) + if (s != null) return; } }"); @@ -183,8 +183,8 @@ class C { void M(string s1, string s2) { - if (s1 is null || - s2 is null) + if (s1 == null || + s2 == null) return; } }"); @@ -211,8 +211,8 @@ class C { void M(string s1, string s2) { - if (s1 is null || - s2 is null) + if (s1 == null || + s2 == null) return; } }"); @@ -222,7 +222,7 @@ void M(string s1, string s2) [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)] public async Task TestMissingIfValueParameterTypeIsUnconstraintGeneric() { - await TestMissingAsync( + await TestInRegularAndScriptAsync( @" class C { @@ -234,6 +234,17 @@ public static void NotNull(T value, string parameterName) } } } +", @" +class C +{ + public static void NotNull(T value, string parameterName) + { + if (value == null) + { + throw new System.ArgumentNullException(parameterName); + } + } +} "); } @@ -259,7 +270,7 @@ class C { public static void NotNull(T value, string parameterName) where T:class { - if (value is null) + if (value == null) { throw new System.ArgumentNullException(parameterName); } diff --git a/src/Features/CSharp/Portable/UseIsNullCheck/CSharpUseIsNullCheckCodeFixProvider.cs b/src/Features/CSharp/Portable/UseIsNullCheck/CSharpUseIsNullCheckCodeFixProvider.cs index dda13baacae92..767d58e0795fc 100644 --- a/src/Features/CSharp/Portable/UseIsNullCheck/CSharpUseIsNullCheckCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UseIsNullCheck/CSharpUseIsNullCheckCodeFixProvider.cs @@ -17,12 +17,16 @@ protected override string GetIsNullTitle() protected override string GetIsNotNullTitle() => GetIsNullTitle(); - protected override SyntaxNode CreateIsNullCheck(SyntaxNode argument) - => SyntaxFactory.IsPatternExpression( + private static SyntaxNode CreateNullCheck(SyntaxNode argument, SyntaxKind comparisonOperator) + => SyntaxFactory.BinaryExpression( + comparisonOperator, (ExpressionSyntax)argument, - SyntaxFactory.ConstantPattern(SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression))).Parenthesize(); + SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)).Parenthesize(); + + protected override SyntaxNode CreateIsNullCheck(SyntaxNode argument) + => CreateNullCheck(argument, SyntaxKind.EqualsExpression); protected override SyntaxNode CreateIsNotNullCheck(SyntaxNode notExpression, SyntaxNode argument) - => ((PrefixUnaryExpressionSyntax)notExpression).WithOperand((ExpressionSyntax)CreateIsNullCheck(argument)); + => CreateNullCheck(argument, SyntaxKind.NotEqualsExpression); } } diff --git a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs index e0464a9faefeb..cebf9a6ddc85d 100644 --- a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs @@ -109,11 +109,6 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, IMethodSymbol refe return; } - if (HasUnconstraintGenericParameter(syntaxFacts, semanticModel, arguments[0], arguments[1], cancellationToken)) - { - return; - } - var additionalLocations = ImmutableArray.Create(invocation.GetLocation()); var properties = ImmutableDictionary.Empty; @@ -130,22 +125,6 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, IMethodSymbol refe additionalLocations, properties)); } - private static bool HasUnconstraintGenericParameter(ISyntaxFactsService syntaxFacts, SemanticModel semanticModel, SyntaxNode node1, SyntaxNode node2, CancellationToken cancellationToken) - { - var valueNode = syntaxFacts.IsNullLiteralExpression(node1) ? node2 : node1; - var argumentExpression = syntaxFacts.GetExpressionOfArgument(valueNode); - if (argumentExpression != null) - { - var parameterType = semanticModel.GetTypeInfo(argumentExpression, cancellationToken).Type; - if (parameterType is ITypeParameterSymbol typeParameter) - { - return !typeParameter.HasReferenceTypeConstraint; - } - } - - return false; - } - private static bool MatchesPattern(ISyntaxFactsService syntaxFacts, SyntaxNode node1, SyntaxNode node2) => syntaxFacts.IsNullLiteralExpression(syntaxFacts.GetExpressionOfArgument(node1)) && !syntaxFacts.IsNullLiteralExpression(syntaxFacts.GetExpressionOfArgument(node2)); From 24fddfff1a137e0280bad05914737ce311241f0f Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Mon, 15 Jan 2018 15:50:32 +0100 Subject: [PATCH 3/8] Added check for value type constraint parameter. --- .../UseIsNullCheck/UseIsNullCheckTests.cs | 37 ++++++++++++++----- .../UseIsNullCheck/UseIsNullCheckTests.vb | 15 ++++++++ .../AbstractUseIsNullDiagnosticAnalyzer.cs | 24 +++++++++++- 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs b/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs index 65ce66c37e560..da5da56a6bfd1 100644 --- a/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs +++ b/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs @@ -226,22 +226,22 @@ await TestInRegularAndScriptAsync( @" class C { - public static void NotNull(T value, string parameterName) + public static void NotNull(T value) { if ([||]ReferenceEquals(value, null)) { - throw new System.ArgumentNullException(parameterName); + return; } } } ", @" class C { - public static void NotNull(T value, string parameterName) + public static void NotNull(T value) { if (value == null) { - throw new System.ArgumentNullException(parameterName); + return; } } } @@ -250,17 +250,17 @@ public static void NotNull(T value, string parameterName) [WorkItem(23581, "https://github.com/dotnet/roslyn/issues/23581")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)] - public async Task TestValueParameterTypeIsConstraintGeneric() + public async Task TestValueParameterTypeIsRefConstraintGeneric() { await TestInRegularAndScriptAsync( @" class C { - public static void NotNull(T value, string parameterName) where T:class + public static void NotNull(T value) where T:class { if ([||]ReferenceEquals(value, null)) { - throw new System.ArgumentNullException(parameterName); + return; } } } @@ -268,11 +268,30 @@ public static void NotNull(T value, string parameterName) where T:class @" class C { - public static void NotNull(T value, string parameterName) where T:class + public static void NotNull(T value) where T:class { if (value == null) { - throw new System.ArgumentNullException(parameterName); + return; + } + } +} +"); + } + + [WorkItem(23581, "https://github.com/dotnet/roslyn/issues/23581")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)] + public async Task TestValueParameterTypeIsValueConstraintGeneric() + { + await TestMissingAsync( +@" +class C +{ + public static void NotNull(T value) where T:struct + { + if ([||]ReferenceEquals(value, null)) + { + return; } } } diff --git a/src/EditorFeatures/VisualBasicTest/UseIsNullCheck/UseIsNullCheckTests.vb b/src/EditorFeatures/VisualBasicTest/UseIsNullCheck/UseIsNullCheckTests.vb index 1c3d85e6c58d7..ce51abf089170 100644 --- a/src/EditorFeatures/VisualBasicTest/UseIsNullCheck/UseIsNullCheckTests.vb +++ b/src/EditorFeatures/VisualBasicTest/UseIsNullCheck/UseIsNullCheckTests.vb @@ -179,6 +179,21 @@ class C return end if end sub +end class") + End Function + + + + Public Async Function TestValueParameterTypeIsValueConstraintGeneric() As Task + Await TestMissingInRegularAndScriptAsync( +"Imports System + +class C + sub M(Of T As Structure)(v as T) + if ([||]ReferenceEquals(Nothing, v)) + return + end if + end sub end class") End Function End Class diff --git a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs index cebf9a6ddc85d..35ffeb63b211e 100644 --- a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs @@ -28,7 +28,8 @@ public override bool OpenFileOnly(Workspace workspace) => false; protected override void InitializeWorker(AnalysisContext context) - => context.RegisterCompilationStartAction(compilationContext => { + => context.RegisterCompilationStartAction(compilationContext => + { var objectType = compilationContext.Compilation.GetSpecialType(SpecialType.System_Object); if (objectType != null) { @@ -109,6 +110,11 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, IMethodSymbol refe return; } + if (HasValueTypeConstraintGenericParameter(syntaxFacts, semanticModel, arguments[0], arguments[1], cancellationToken)) + { + return; + } + var additionalLocations = ImmutableArray.Create(invocation.GetLocation()); var properties = ImmutableDictionary.Empty; @@ -125,6 +131,22 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, IMethodSymbol refe additionalLocations, properties)); } + private static bool HasValueTypeConstraintGenericParameter(ISyntaxFactsService syntaxFacts, SemanticModel semanticModel, SyntaxNode node1, SyntaxNode node2, CancellationToken cancellationToken) + { + var valueNode = syntaxFacts.IsNullLiteralExpression(syntaxFacts.GetExpressionOfArgument(node1)) ? node2 : node1; + var argumentExpression = syntaxFacts.GetExpressionOfArgument(valueNode); + if (argumentExpression != null) + { + var parameterType = semanticModel.GetTypeInfo(argumentExpression, cancellationToken).Type; + if (parameterType is ITypeParameterSymbol typeParameter) + { + return typeParameter.HasValueTypeConstraint; + } + } + + return false; + } + private static bool MatchesPattern(ISyntaxFactsService syntaxFacts, SyntaxNode node1, SyntaxNode node2) => syntaxFacts.IsNullLiteralExpression(syntaxFacts.GetExpressionOfArgument(node1)) && !syntaxFacts.IsNullLiteralExpression(syntaxFacts.GetExpressionOfArgument(node2)); From f94eaa9d3101405c033a4ced8dbd6e248bbdda3b Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 17 Jan 2018 13:20:12 +0100 Subject: [PATCH 4/8] Changed logic for generic parameter constraints: case 'type constraint': no fix offered case 'reference constraint': C# use '==null' instead of 'is null' default: C# 'is null', VB 'IsNothing' --- .../UseIsNullCheck/UseIsNullCheckTests.cs | 22 ++++++------ .../CSharpUseIsNullCheckCodeFixProvider.cs | 22 +++++++++--- .../AbstractUseIsNullCodeFixProvider.cs | 12 ++++--- .../AbstractUseIsNullDiagnosticAnalyzer.cs | 36 +++++++++++++++---- ...isualBasicUseIsNullCheckCodeFixProvider.vb | 8 ++--- 5 files changed, 70 insertions(+), 30 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs b/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs index da5da56a6bfd1..084051762ef90 100644 --- a/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs +++ b/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs @@ -36,7 +36,7 @@ class C { void M(string s) { - if (s == null) + if (s is null) return; } }"); @@ -62,7 +62,7 @@ class C { void M(string s) { - if (s == null) + if (s is null) return; } }"); @@ -88,7 +88,7 @@ class C { void M(string s) { - if (s == null) + if (s is null) return; } }"); @@ -114,7 +114,7 @@ class C { void M(string s) { - if (s == null) + if (s is null) return; } }"); @@ -140,7 +140,7 @@ class C { void M(string s) { - if (s != null) + if (!(s is null)) return; } }"); @@ -183,8 +183,8 @@ class C { void M(string s1, string s2) { - if (s1 == null || - s2 == null) + if (s1 is null || + s2 is null) return; } }"); @@ -211,8 +211,8 @@ class C { void M(string s1, string s2) { - if (s1 == null || - s2 == null) + if (s1 is null || + s2 is null) return; } }"); @@ -220,7 +220,7 @@ void M(string s1, string s2) [WorkItem(23581, "https://github.com/dotnet/roslyn/issues/23581")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)] - public async Task TestMissingIfValueParameterTypeIsUnconstraintGeneric() + public async Task TestValueParameterTypeIsUnconstraintGeneric() { await TestInRegularAndScriptAsync( @" @@ -270,7 +270,7 @@ class C { public static void NotNull(T value) where T:class { - if (value == null) + if (value is null) { return; } diff --git a/src/Features/CSharp/Portable/UseIsNullCheck/CSharpUseIsNullCheckCodeFixProvider.cs b/src/Features/CSharp/Portable/UseIsNullCheck/CSharpUseIsNullCheckCodeFixProvider.cs index 767d58e0795fc..d3ab0c28009e8 100644 --- a/src/Features/CSharp/Portable/UseIsNullCheck/CSharpUseIsNullCheckCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UseIsNullCheck/CSharpUseIsNullCheckCodeFixProvider.cs @@ -17,16 +17,28 @@ protected override string GetIsNullTitle() protected override string GetIsNotNullTitle() => GetIsNullTitle(); - private static SyntaxNode CreateNullCheck(SyntaxNode argument, SyntaxKind comparisonOperator) + private static SyntaxNode CreateEqualsNullCheck(SyntaxNode argument, SyntaxKind comparisonOperator) => SyntaxFactory.BinaryExpression( comparisonOperator, (ExpressionSyntax)argument, SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)).Parenthesize(); - protected override SyntaxNode CreateIsNullCheck(SyntaxNode argument) - => CreateNullCheck(argument, SyntaxKind.EqualsExpression); + private static SyntaxNode CreateIsNullCheck(SyntaxNode argument) + => SyntaxFactory.IsPatternExpression( + (ExpressionSyntax)argument, + SyntaxFactory.ConstantPattern(SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression))).Parenthesize(); + + private static SyntaxNode CreateIsNotNullCheck(SyntaxNode notExpression, SyntaxNode argument) + => ((PrefixUnaryExpressionSyntax)notExpression).WithOperand((ExpressionSyntax)CreateIsNullCheck(argument)); + + protected override SyntaxNode CreateNullCheck(SyntaxNode argument, bool isUnconstraintGeneric) + => isUnconstraintGeneric + ? CreateEqualsNullCheck(argument, SyntaxKind.EqualsExpression) + : CreateIsNullCheck(argument); - protected override SyntaxNode CreateIsNotNullCheck(SyntaxNode notExpression, SyntaxNode argument) - => CreateNullCheck(argument, SyntaxKind.NotEqualsExpression); + protected override SyntaxNode CreateNotNullCheck(SyntaxNode notExpression, SyntaxNode argument, bool isUnconstraintGeneric) + => isUnconstraintGeneric + ? CreateEqualsNullCheck(argument, SyntaxKind.NotEqualsExpression) + : CreateIsNotNullCheck(notExpression, argument); } } diff --git a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullCodeFixProvider.cs b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullCodeFixProvider.cs index e200f35af360d..85399985def27 100644 --- a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullCodeFixProvider.cs +++ b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullCodeFixProvider.cs @@ -18,14 +18,15 @@ namespace Microsoft.CodeAnalysis.UseIsNullCheck internal abstract class AbstractUseIsNullCheckCodeFixProvider : SyntaxEditorBasedCodeFixProvider { public const string Negated = nameof(Negated); + public const string UnconstraintGeneric = nameof(UnconstraintGeneric); public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(IDEDiagnosticIds.UseIsNullCheckDiagnosticId); protected abstract string GetIsNullTitle(); protected abstract string GetIsNotNullTitle(); - protected abstract SyntaxNode CreateIsNullCheck(SyntaxNode argument); - protected abstract SyntaxNode CreateIsNotNullCheck(SyntaxNode notExpression, SyntaxNode argument); + protected abstract SyntaxNode CreateNullCheck(SyntaxNode argument, bool isUnconstraintGeneric); + protected abstract SyntaxNode CreateNotNullCheck(SyntaxNode notExpression, SyntaxNode argument, bool isUnconstraintGeneric); public override Task RegisterCodeFixesAsync(CodeFixContext context) { @@ -49,6 +50,7 @@ protected override Task FixAllAsync( { var invocation = diagnostic.AdditionalLocations[0].FindNode(getInnermostNodeForTie: true, cancellationToken: cancellationToken); var negate = diagnostic.Properties.ContainsKey(Negated); + var isUnconstraintGeneric = diagnostic.Properties.ContainsKey(UnconstraintGeneric); var arguments = syntaxFacts.GetArgumentsOfInvocationExpression(invocation); var argument = syntaxFacts.IsNullLiteralExpression(syntaxFacts.GetExpressionOfArgument(arguments[0])) @@ -56,7 +58,9 @@ protected override Task FixAllAsync( : syntaxFacts.GetExpressionOfArgument(arguments[0]); var toReplace = negate ? invocation.Parent : invocation; - var replacement = negate ? CreateIsNotNullCheck(invocation.Parent, argument) : CreateIsNullCheck(argument); + var replacement = negate + ? CreateNotNullCheck(invocation.Parent, argument, isUnconstraintGeneric) + : CreateNullCheck(argument, isUnconstraintGeneric); editor.ReplaceNode( toReplace, @@ -68,7 +72,7 @@ protected override Task FixAllAsync( private class MyCodeAction : CodeAction.DocumentChangeAction { - public MyCodeAction(string title, Func> createChangedDocument) + public MyCodeAction(string title, Func> createChangedDocument) : base(title, createChangedDocument, title) { } diff --git a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs index 35ffeb63b211e..8e634332e185d 100644 --- a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs @@ -14,6 +14,15 @@ internal abstract class AbstractUseIsNullCheckDiagnosticAnalyzer< : AbstractCodeStyleDiagnosticAnalyzer where TLanguageKindEnum : struct { + + private enum ConstraintParameterKind + { + NotGeneric, + UnconstraintGeneric, + ReferenceTypeConstraint, + ValueTypeConstraint, + } + protected AbstractUseIsNullCheckDiagnosticAnalyzer(LocalizableString title) : base(IDEDiagnosticIds.UseIsNullCheckDiagnosticId, title, @@ -110,13 +119,18 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, IMethodSymbol refe return; } - if (HasValueTypeConstraintGenericParameter(syntaxFacts, semanticModel, arguments[0], arguments[1], cancellationToken)) + var properties = ImmutableDictionary.Empty; + + switch (GetGenericParameterConstraintType(syntaxFacts, semanticModel, arguments[0], arguments[1], cancellationToken)) { - return; + case ConstraintParameterKind.ValueTypeConstraint: + return; + case ConstraintParameterKind.UnconstraintGeneric: + properties = properties.Add(AbstractUseIsNullCheckCodeFixProvider.UnconstraintGeneric, ""); + break; } var additionalLocations = ImmutableArray.Create(invocation.GetLocation()); - var properties = ImmutableDictionary.Empty; var negated = syntaxFacts.IsLogicalNotExpression(invocation.Parent); if (negated) @@ -131,7 +145,7 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, IMethodSymbol refe additionalLocations, properties)); } - private static bool HasValueTypeConstraintGenericParameter(ISyntaxFactsService syntaxFacts, SemanticModel semanticModel, SyntaxNode node1, SyntaxNode node2, CancellationToken cancellationToken) + private static ConstraintParameterKind GetGenericParameterConstraintType(ISyntaxFactsService syntaxFacts, SemanticModel semanticModel, SyntaxNode node1, SyntaxNode node2, CancellationToken cancellationToken) { var valueNode = syntaxFacts.IsNullLiteralExpression(syntaxFacts.GetExpressionOfArgument(node1)) ? node2 : node1; var argumentExpression = syntaxFacts.GetExpressionOfArgument(valueNode); @@ -140,11 +154,21 @@ private static bool HasValueTypeConstraintGenericParameter(ISyntaxFactsService s var parameterType = semanticModel.GetTypeInfo(argumentExpression, cancellationToken).Type; if (parameterType is ITypeParameterSymbol typeParameter) { - return typeParameter.HasValueTypeConstraint; + if (typeParameter.HasReferenceTypeConstraint) + { + return ConstraintParameterKind.ReferenceTypeConstraint; + } + + if (typeParameter.HasValueTypeConstraint) + { + return ConstraintParameterKind.ValueTypeConstraint; + } + + return ConstraintParameterKind.UnconstraintGeneric; } } - return false; + return ConstraintParameterKind.NotGeneric; } private static bool MatchesPattern(ISyntaxFactsService syntaxFacts, SyntaxNode node1, SyntaxNode node2) diff --git a/src/Features/VisualBasic/Portable/UseIsNullCheck/VisualBasicUseIsNullCheckCodeFixProvider.vb b/src/Features/VisualBasic/Portable/UseIsNullCheck/VisualBasicUseIsNullCheckCodeFixProvider.vb index 205dede6ef8de..ce9558bc29d31 100644 --- a/src/Features/VisualBasic/Portable/UseIsNullCheck/VisualBasicUseIsNullCheckCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/UseIsNullCheck/VisualBasicUseIsNullCheckCodeFixProvider.vb @@ -11,20 +11,20 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseIsNullCheck Inherits AbstractUseIsNullCheckCodeFixProvider Protected Overrides Function GetIsNullTitle() As String - Return VBFeaturesResources.use_Is_Nothing_check + Return VBFeaturesResources.Use_Is_Nothing_check End Function Protected Overrides Function GetIsNotNullTitle() As String - Return VBFeaturesResources.use_IsNot_Nothing_check + Return VBFeaturesResources.Use_IsNot_Nothing_check End Function - Protected Overrides Function CreateIsNullCheck(argument As SyntaxNode) As SyntaxNode + Protected Overrides Function CreateNullCheck(argument As SyntaxNode, isUnconstraintGeneric As Boolean) As SyntaxNode Return SyntaxFactory.IsExpression( DirectCast(argument, ExpressionSyntax).Parenthesize(), SyntaxFactory.NothingLiteralExpression(SyntaxFactory.Token(SyntaxKind.NothingKeyword))).Parenthesize() End Function - Protected Overrides Function CreateIsNotNullCheck(notExpression As SyntaxNode, argument As SyntaxNode) As SyntaxNode + Protected Overrides Function CreateNotNullCheck(notExpression As SyntaxNode, argument As SyntaxNode, isUnconstraintGeneric As Boolean) As SyntaxNode Return SyntaxFactory.IsNotExpression( DirectCast(argument, ExpressionSyntax).Parenthesize(), SyntaxFactory.NothingLiteralExpression(SyntaxFactory.Token(SyntaxKind.NothingKeyword))).Parenthesize() From 5145252b42c91b052b68ae183d32d7b6ba7ce5cf Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 18 Jan 2018 10:27:29 +0100 Subject: [PATCH 5/8] Simplify handling of type constraints. --- .../AbstractUseIsNullCodeFixProvider.cs | 4 +- .../AbstractUseIsNullDiagnosticAnalyzer.cs | 46 +++++++------------ 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullCodeFixProvider.cs b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullCodeFixProvider.cs index 85399985def27..3b44f28f49ad2 100644 --- a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullCodeFixProvider.cs +++ b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullCodeFixProvider.cs @@ -18,7 +18,7 @@ namespace Microsoft.CodeAnalysis.UseIsNullCheck internal abstract class AbstractUseIsNullCheckCodeFixProvider : SyntaxEditorBasedCodeFixProvider { public const string Negated = nameof(Negated); - public const string UnconstraintGeneric = nameof(UnconstraintGeneric); + public const string UnconstrainedGeneric = nameof(UnconstrainedGeneric); public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(IDEDiagnosticIds.UseIsNullCheckDiagnosticId); @@ -50,7 +50,7 @@ protected override Task FixAllAsync( { var invocation = diagnostic.AdditionalLocations[0].FindNode(getInnermostNodeForTie: true, cancellationToken: cancellationToken); var negate = diagnostic.Properties.ContainsKey(Negated); - var isUnconstraintGeneric = diagnostic.Properties.ContainsKey(UnconstraintGeneric); + var isUnconstraintGeneric = diagnostic.Properties.ContainsKey(UnconstrainedGeneric); var arguments = syntaxFacts.GetArgumentsOfInvocationExpression(invocation); var argument = syntaxFacts.IsNullLiteralExpression(syntaxFacts.GetExpressionOfArgument(arguments[0])) diff --git a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs index 8e634332e185d..6e04cbc45a08d 100644 --- a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs @@ -15,14 +15,6 @@ internal abstract class AbstractUseIsNullCheckDiagnosticAnalyzer< where TLanguageKindEnum : struct { - private enum ConstraintParameterKind - { - NotGeneric, - UnconstraintGeneric, - ReferenceTypeConstraint, - ValueTypeConstraint, - } - protected AbstractUseIsNullCheckDiagnosticAnalyzer(LocalizableString title) : base(IDEDiagnosticIds.UseIsNullCheckDiagnosticId, title, @@ -121,13 +113,22 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, IMethodSymbol refe var properties = ImmutableDictionary.Empty; - switch (GetGenericParameterConstraintType(syntaxFacts, semanticModel, arguments[0], arguments[1], cancellationToken)) + var genericParameterSymbol = GetGenericParameterSymbol(syntaxFacts, semanticModel, arguments[0], arguments[1], cancellationToken); + if (genericParameterSymbol != null) { - case ConstraintParameterKind.ValueTypeConstraint: + if (genericParameterSymbol.HasValueTypeConstraint) + { + // 'is null' would generate error CS0403: Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead. + // '== null' would generate error CS0019: Operator '==' cannot be applied to operands of type 'T' and '' + // 'Is Nothing' would generate error BC30020: 'Is' operator does not accept operands of type 'T'. Operands must be reference or nullable types. return; - case ConstraintParameterKind.UnconstraintGeneric: - properties = properties.Add(AbstractUseIsNullCheckCodeFixProvider.UnconstraintGeneric, ""); - break; + } + + if (!genericParameterSymbol.HasReferenceTypeConstraint) + { + // Needs special casing for C# + properties = properties.Add(AbstractUseIsNullCheckCodeFixProvider.UnconstrainedGeneric, ""); + } } var additionalLocations = ImmutableArray.Create(invocation.GetLocation()); @@ -145,30 +146,17 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, IMethodSymbol refe additionalLocations, properties)); } - private static ConstraintParameterKind GetGenericParameterConstraintType(ISyntaxFactsService syntaxFacts, SemanticModel semanticModel, SyntaxNode node1, SyntaxNode node2, CancellationToken cancellationToken) + private static ITypeParameterSymbol GetGenericParameterSymbol(ISyntaxFactsService syntaxFacts, SemanticModel semanticModel, SyntaxNode node1, SyntaxNode node2, CancellationToken cancellationToken) { var valueNode = syntaxFacts.IsNullLiteralExpression(syntaxFacts.GetExpressionOfArgument(node1)) ? node2 : node1; var argumentExpression = syntaxFacts.GetExpressionOfArgument(valueNode); if (argumentExpression != null) { var parameterType = semanticModel.GetTypeInfo(argumentExpression, cancellationToken).Type; - if (parameterType is ITypeParameterSymbol typeParameter) - { - if (typeParameter.HasReferenceTypeConstraint) - { - return ConstraintParameterKind.ReferenceTypeConstraint; - } - - if (typeParameter.HasValueTypeConstraint) - { - return ConstraintParameterKind.ValueTypeConstraint; - } - - return ConstraintParameterKind.UnconstraintGeneric; - } + return parameterType as ITypeParameterSymbol; } - return ConstraintParameterKind.NotGeneric; + return default; } private static bool MatchesPattern(ISyntaxFactsService syntaxFacts, SyntaxNode node1, SyntaxNode node2) From 1979ff8ebfa8f85e1e0a27a04271cbc8cb34367c Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 18 Jan 2018 11:31:59 +0100 Subject: [PATCH 6/8] Added code comment with link to dotnet/csharplang#1261. --- .../UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs index 6e04cbc45a08d..78325014bd76f 100644 --- a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs @@ -126,7 +126,9 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, IMethodSymbol refe if (!genericParameterSymbol.HasReferenceTypeConstraint) { - // Needs special casing for C# + // Needs special casing for C# as long as + // https://github.com/dotnet/csharplang/issues/1261 + // is not implemented. properties = properties.Add(AbstractUseIsNullCheckCodeFixProvider.UnconstrainedGeneric, ""); } } From 9bb2dd8013a918ac9377aaf7d14c35b41344f68f Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 23 Jan 2018 09:46:28 +0100 Subject: [PATCH 7/8] Spelling correction --- .../CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs | 2 +- .../CSharpUseIsNullCheckCodeFixProvider.cs | 8 ++++---- .../UseIsNullCheck/AbstractUseIsNullCodeFixProvider.cs | 10 +++++----- .../VisualBasicUseIsNullCheckCodeFixProvider.vb | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs b/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs index 084051762ef90..d7c0a54665d5d 100644 --- a/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs +++ b/src/EditorFeatures/CSharpTest/UseIsNullCheck/UseIsNullCheckTests.cs @@ -220,7 +220,7 @@ void M(string s1, string s2) [WorkItem(23581, "https://github.com/dotnet/roslyn/issues/23581")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)] - public async Task TestValueParameterTypeIsUnconstraintGeneric() + public async Task TestValueParameterTypeIsUnconstrainedGeneric() { await TestInRegularAndScriptAsync( @" diff --git a/src/Features/CSharp/Portable/UseIsNullCheck/CSharpUseIsNullCheckCodeFixProvider.cs b/src/Features/CSharp/Portable/UseIsNullCheck/CSharpUseIsNullCheckCodeFixProvider.cs index d3ab0c28009e8..5ddf7e2797233 100644 --- a/src/Features/CSharp/Portable/UseIsNullCheck/CSharpUseIsNullCheckCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UseIsNullCheck/CSharpUseIsNullCheckCodeFixProvider.cs @@ -31,13 +31,13 @@ private static SyntaxNode CreateIsNullCheck(SyntaxNode argument) private static SyntaxNode CreateIsNotNullCheck(SyntaxNode notExpression, SyntaxNode argument) => ((PrefixUnaryExpressionSyntax)notExpression).WithOperand((ExpressionSyntax)CreateIsNullCheck(argument)); - protected override SyntaxNode CreateNullCheck(SyntaxNode argument, bool isUnconstraintGeneric) - => isUnconstraintGeneric + protected override SyntaxNode CreateNullCheck(SyntaxNode argument, bool isUnconstrainedGeneric) + => isUnconstrainedGeneric ? CreateEqualsNullCheck(argument, SyntaxKind.EqualsExpression) : CreateIsNullCheck(argument); - protected override SyntaxNode CreateNotNullCheck(SyntaxNode notExpression, SyntaxNode argument, bool isUnconstraintGeneric) - => isUnconstraintGeneric + protected override SyntaxNode CreateNotNullCheck(SyntaxNode notExpression, SyntaxNode argument, bool isUnconstrainedGeneric) + => isUnconstrainedGeneric ? CreateEqualsNullCheck(argument, SyntaxKind.NotEqualsExpression) : CreateIsNotNullCheck(notExpression, argument); } diff --git a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullCodeFixProvider.cs b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullCodeFixProvider.cs index 3b44f28f49ad2..f85ad74f52fff 100644 --- a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullCodeFixProvider.cs +++ b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullCodeFixProvider.cs @@ -25,8 +25,8 @@ public override ImmutableArray FixableDiagnosticIds protected abstract string GetIsNullTitle(); protected abstract string GetIsNotNullTitle(); - protected abstract SyntaxNode CreateNullCheck(SyntaxNode argument, bool isUnconstraintGeneric); - protected abstract SyntaxNode CreateNotNullCheck(SyntaxNode notExpression, SyntaxNode argument, bool isUnconstraintGeneric); + protected abstract SyntaxNode CreateNullCheck(SyntaxNode argument, bool isUnconstrainedGeneric); + protected abstract SyntaxNode CreateNotNullCheck(SyntaxNode notExpression, SyntaxNode argument, bool isUnconstrainedGeneric); public override Task RegisterCodeFixesAsync(CodeFixContext context) { @@ -50,7 +50,7 @@ protected override Task FixAllAsync( { var invocation = diagnostic.AdditionalLocations[0].FindNode(getInnermostNodeForTie: true, cancellationToken: cancellationToken); var negate = diagnostic.Properties.ContainsKey(Negated); - var isUnconstraintGeneric = diagnostic.Properties.ContainsKey(UnconstrainedGeneric); + var isUnconstrainedGeneric = diagnostic.Properties.ContainsKey(UnconstrainedGeneric); var arguments = syntaxFacts.GetArgumentsOfInvocationExpression(invocation); var argument = syntaxFacts.IsNullLiteralExpression(syntaxFacts.GetExpressionOfArgument(arguments[0])) @@ -59,8 +59,8 @@ protected override Task FixAllAsync( var toReplace = negate ? invocation.Parent : invocation; var replacement = negate - ? CreateNotNullCheck(invocation.Parent, argument, isUnconstraintGeneric) - : CreateNullCheck(argument, isUnconstraintGeneric); + ? CreateNotNullCheck(invocation.Parent, argument, isUnconstrainedGeneric) + : CreateNullCheck(argument, isUnconstrainedGeneric); editor.ReplaceNode( toReplace, diff --git a/src/Features/VisualBasic/Portable/UseIsNullCheck/VisualBasicUseIsNullCheckCodeFixProvider.vb b/src/Features/VisualBasic/Portable/UseIsNullCheck/VisualBasicUseIsNullCheckCodeFixProvider.vb index ce9558bc29d31..84579b2747265 100644 --- a/src/Features/VisualBasic/Portable/UseIsNullCheck/VisualBasicUseIsNullCheckCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/UseIsNullCheck/VisualBasicUseIsNullCheckCodeFixProvider.vb @@ -18,13 +18,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseIsNullCheck Return VBFeaturesResources.Use_IsNot_Nothing_check End Function - Protected Overrides Function CreateNullCheck(argument As SyntaxNode, isUnconstraintGeneric As Boolean) As SyntaxNode + Protected Overrides Function CreateNullCheck(argument As SyntaxNode, isUnconstrainedGeneric As Boolean) As SyntaxNode Return SyntaxFactory.IsExpression( DirectCast(argument, ExpressionSyntax).Parenthesize(), SyntaxFactory.NothingLiteralExpression(SyntaxFactory.Token(SyntaxKind.NothingKeyword))).Parenthesize() End Function - Protected Overrides Function CreateNotNullCheck(notExpression As SyntaxNode, argument As SyntaxNode, isUnconstraintGeneric As Boolean) As SyntaxNode + Protected Overrides Function CreateNotNullCheck(notExpression As SyntaxNode, argument As SyntaxNode, isUnconstrainedGeneric As Boolean) As SyntaxNode Return SyntaxFactory.IsNotExpression( DirectCast(argument, ExpressionSyntax).Parenthesize(), SyntaxFactory.NothingLiteralExpression(SyntaxFactory.Token(SyntaxKind.NothingKeyword))).Parenthesize() From bffe221104d91b090f7d7271b91ae7995362b9e9 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Fri, 26 Jan 2018 14:46:25 +0100 Subject: [PATCH 8/8] Changed code comment link to dotnet/csharplang#1284. --- .../UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs index 78325014bd76f..d5375872deacf 100644 --- a/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/UseIsNullCheck/AbstractUseIsNullDiagnosticAnalyzer.cs @@ -127,7 +127,7 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, IMethodSymbol refe if (!genericParameterSymbol.HasReferenceTypeConstraint) { // Needs special casing for C# as long as - // https://github.com/dotnet/csharplang/issues/1261 + // https://github.com/dotnet/csharplang/issues/1284 // is not implemented. properties = properties.Add(AbstractUseIsNullCheckCodeFixProvider.UnconstrainedGeneric, ""); }