diff --git a/src/EditorFeatures/VisualBasicTest/GenerateConstructor/GenerateConstructorTests.vb b/src/EditorFeatures/VisualBasicTest/GenerateConstructor/GenerateConstructorTests.vb index 4a216cd2d9f50..a89f63bd239c7 100644 --- a/src/EditorFeatures/VisualBasicTest/GenerateConstructor/GenerateConstructorTests.vb +++ b/src/EditorFeatures/VisualBasicTest/GenerateConstructor/GenerateConstructorTests.vb @@ -2179,5 +2179,117 @@ Class C End Class ") End Function + + + + Public Async Function TestOmittedParameter() As Task + + Await TestInRegularAndScriptAsync( +"Class C + Private _a As Integer + + Public Sub New(Optional a As Integer = 1) + Me._a = a + End Sub + + Public Function M() As C + Return New C(, [||]2) + End Function +End Class +", +"Class C + Private _a As Integer + Private v As Integer + + Public Sub New(Optional a As Integer = 1) + Me._a = a + End Sub + + Public Sub New(Optional a As Integer = 1, Optional v As Integer = Nothing) + Me.New(a) + Me.v = v + End Sub + + Public Function M() As C + Return New C(, 2) + End Function +End Class +") + End Function + + + + Public Async Function TestOmittedParameterAtEnd() As Task + + Await TestInRegularAndScriptAsync( +"Class C + Private _a As Integer + + Public Sub New(Optional a As Integer = 1) + Me._a = a + End Sub + + Public Function M() As C + Return New C(1,[||]) + End Function +End Class +", +"Class C + Private _a As Integer + Private p As Object + + Public Sub New(Optional a As Integer = 1) + Me._a = a + End Sub + + Public Sub New(Optional a As Integer = 1, Optional p As Object = Nothing) + Me.New(a) + Me.p = p + End Sub + + Public Function M() As C + Return New C(1,) + End Function +End Class +") + End Function + + + + Public Async Function TestOmittedParameterAtStartAndEnd() As Task + + Await TestInRegularAndScriptAsync( +"Class C + Private _a As Integer + + Public Sub New(Optional a As Integer = 1) + Me._a = a + End Sub + + Public Function M() As C + Return New C(,[||]) + End Function +End Class +", +"Class C + Private _a As Integer + Private p As Object + + Public Sub New(Optional a As Integer = 1) + Me._a = a + End Sub + + Public Sub New(Optional a As Integer = 1, Optional p As Object = Nothing) + Me.New(a) + Me.p = p + End Sub + + Public Function M() As C + Return New C(,) + End Function +End Class +") + End Function + End Class End Namespace diff --git a/src/Features/Core/Portable/CodeFixes/GenerateMember/AbstractGenerateMemberCodeFixProvider.cs b/src/Features/Core/Portable/CodeFixes/GenerateMember/AbstractGenerateMemberCodeFixProvider.cs index cc1597ade5bb8..db10642703a8d 100644 --- a/src/Features/Core/Portable/CodeFixes/GenerateMember/AbstractGenerateMemberCodeFixProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/GenerateMember/AbstractGenerateMemberCodeFixProvider.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -19,7 +17,7 @@ namespace Microsoft.CodeAnalysis.CodeFixes.GenerateMember { internal abstract class AbstractGenerateMemberCodeFixProvider : CodeFixProvider { - public override FixAllProvider GetFixAllProvider() + public override FixAllProvider? GetFixAllProvider() { // Fix All is not supported by this code fix return null; @@ -39,9 +37,9 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) var diagnostic = context.Diagnostics.First(); var document = context.Document; - var syntaxFacts = document.GetLanguageService(); + var syntaxFacts = document.GetRequiredLanguageService(); - var root = await document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var root = await document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var names = GetTargetNodes(syntaxFacts, root, context.Span, diagnostic); foreach (var name in names) { diff --git a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.State.cs b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.State.cs index 59c12745260db..f6bbfb840aaaf 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.State.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.State.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -39,7 +37,7 @@ protected internal class State private ImmutableArray _arguments; // The type we're creating a constructor for. Will be a class or struct type. - public INamedTypeSymbol TypeToGenerateIn { get; private set; } + public INamedTypeSymbol? TypeToGenerateIn { get; private set; } private ImmutableArray _parameterRefKinds; public ImmutableArray ParameterTypes; @@ -47,10 +45,10 @@ protected internal class State public SyntaxToken Token { get; private set; } public bool IsConstructorInitializerGeneration { get; private set; } - private IMethodSymbol _delegatedConstructor; + private IMethodSymbol? _delegatedConstructor; private ImmutableArray _parameters; - private ImmutableDictionary _parameterToExistingMemberMap; + private ImmutableDictionary? _parameterToExistingMemberMap; public ImmutableDictionary ParameterToNewFieldMap { get; private set; } public ImmutableDictionary ParameterToNewPropertyMap { get; private set; } @@ -63,9 +61,12 @@ private State(TService service, SemanticDocument document, NamingRule fieldNamin _fieldNamingRule = fieldNamingRule; _propertyNamingRule = propertyNamingRule; _parameterNamingRule = parameterNamingRule; + + ParameterToNewFieldMap = ImmutableDictionary.Empty; + ParameterToNewPropertyMap = ImmutableDictionary.Empty; } - public static async Task GenerateAsync( + public static async Task GenerateAsync( TService service, SemanticDocument document, SyntaxNode node, @@ -107,6 +108,7 @@ private async Task TryInitializeAsync( return false; } + Contract.ThrowIfNull(TypeToGenerateIn); if (!CodeGenerator.CanAdd(_document.Project.Solution, TypeToGenerateIn, cancellationToken)) return false; @@ -173,11 +175,14 @@ private bool TryInitializeDelegatedConstructor(CancellationToken cancellationTok return true; } - private IMethodSymbol FindConstructorToDelegateTo( + private IMethodSymbol? FindConstructorToDelegateTo( ImmutableArray allParameters, - ImmutableArray allExpressions, + ImmutableArray allExpressions, CancellationToken cancellationToken) { + Contract.ThrowIfNull(TypeToGenerateIn); + Contract.ThrowIfNull(TypeToGenerateIn.BaseType); + for (var i = allParameters.Length; i > 0; i--) { var parameters = allParameters.TakeAsArray(i); @@ -191,12 +196,14 @@ private IMethodSymbol FindConstructorToDelegateTo( return null; } - private IMethodSymbol FindConstructorToDelegateTo( + private IMethodSymbol? FindConstructorToDelegateTo( ImmutableArray parameters, - ImmutableArray expressions, + ImmutableArray expressions, ImmutableArray constructors, CancellationToken cancellationToken) { + Contract.ThrowIfNull(TypeToGenerateIn); + foreach (var constructor in constructors) { // Don't bother delegating to an implicit constructor. We don't want to add `: base()` as that's just @@ -226,8 +233,10 @@ private IMethodSymbol FindConstructorToDelegateTo( private bool ClashesWithExistingConstructor() { + Contract.ThrowIfNull(TypeToGenerateIn); + var destinationProvider = _document.Project.Solution.Workspace.Services.GetLanguageServices(TypeToGenerateIn.Language); - var syntaxFacts = destinationProvider.GetService(); + var syntaxFacts = destinationProvider.GetRequiredService(); return TypeToGenerateIn.InstanceConstructors.Any(c => Matches(c, syntaxFacts)); } @@ -508,6 +517,8 @@ where m.Name.Equals(expectedFieldName, StringComparison.OrdinalIgnoreCase) private IEnumerable GetUnavailableMemberNames() { + Contract.ThrowIfNull(TypeToGenerateIn); + return TypeToGenerateIn.MemberNames.Concat( from type in TypeToGenerateIn.GetBaseTypes() from member in type.GetMembers() @@ -557,12 +568,14 @@ public async Task GetChangedDocumentAsync( await GenerateMemberDelegatingConstructorAsync(document, withFields, withProperties, cancellationToken).ConfigureAwait(false); } - private async Task GenerateThisOrBaseDelegatingConstructorAsync( + private async Task GenerateThisOrBaseDelegatingConstructorAsync( Document document, bool withFields, bool withProperties, CancellationToken cancellationToken) { if (_delegatedConstructor == null) return null; + Contract.ThrowIfNull(TypeToGenerateIn); + var provider = document.Project.Solution.Workspace.Services.GetLanguageServices(TypeToGenerateIn.Language); var (members, assignments) = await GenerateMembersAndAssignmentsAsync(document, withFields, withProperties, cancellationToken).ConfigureAwait(false); var isThis = _delegatedConstructor.ContainingType.OriginalDefinition.Equals(TypeToGenerateIn.OriginalDefinition); @@ -581,7 +594,7 @@ private async Task GenerateThisOrBaseDelegatingConstructorAsync( baseConstructorArguments: isThis ? default : delegatingArguments, thisConstructorArguments: isThis ? delegatingArguments : default); - return await provider.GetService().AddMembersAsync( + return await provider.GetRequiredService().AddMembersAsync( document.Project.Solution, TypeToGenerateIn, members.Concat(constructor), @@ -594,6 +607,8 @@ private async Task GenerateThisOrBaseDelegatingConstructorAsync( private async Task<(ImmutableArray, ImmutableArray)> GenerateMembersAndAssignmentsAsync( Document document, bool withFields, bool withProperties, CancellationToken cancellationToken) { + Contract.ThrowIfNull(TypeToGenerateIn); + var provider = document.Project.Solution.Workspace.Services.GetLanguageServices(TypeToGenerateIn.Language); var members = withFields ? SyntaxGeneratorExtensions.CreateFieldsForParameters(_parameters, ParameterToNewFieldMap, IsContainedInUnsafeType) : @@ -615,6 +630,8 @@ private async Task GenerateThisOrBaseDelegatingConstructorAsync( private async Task GenerateMemberDelegatingConstructorAsync( Document document, bool withFields, bool withProperties, CancellationToken cancellationToken) { + Contract.ThrowIfNull(TypeToGenerateIn); + var provider = document.Project.Solution.Workspace.Services.GetLanguageServices(TypeToGenerateIn.Language); var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); @@ -623,7 +640,7 @@ private async Task GenerateMemberDelegatingConstructorAsync( withProperties ? ParameterToNewPropertyMap : ImmutableDictionary.Empty; - return await provider.GetService().AddMembersAsync( + return await provider.GetRequiredService().AddMembersAsync( document.Project.Solution, TypeToGenerateIn, provider.GetService().CreateMemberDelegatingConstructor( diff --git a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs index 391c93153d090..1d58ae04b7ca0 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -86,6 +84,8 @@ public async Task> GenerateConstructorAsync(Document var state = await State.GenerateAsync((TService)this, semanticDocument, node, cancellationToken).ConfigureAwait(false); if (state != null) { + Contract.ThrowIfNull(state.TypeToGenerateIn); + using var _ = ArrayBuilder.GetInstance(out var result); // If we have any fields we'd like to generate, offer a code action to do that. @@ -116,7 +116,7 @@ public async Task> GenerateConstructorAsync(Document return ImmutableArray.Empty; } - protected static bool IsSymbolAccessible(ISymbol symbol, SemanticDocument document) + protected static bool IsSymbolAccessible(ISymbol? symbol, SemanticDocument document) { if (symbol == null) { @@ -157,6 +157,9 @@ protected string GenerateNameForArgument(SemanticModel semanticModel, Argument a if (argument.IsNamed) return argument.Name; + if (argument.Expression is null) + return ITypeSymbolExtensions.DefaultParameterName; + var name = this.GenerateNameForExpression(semanticModel, argument.Expression, cancellationToken); return string.IsNullOrEmpty(name) ? ITypeSymbolExtensions.DefaultParameterName : name; } diff --git a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/Argument.cs b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/Argument.cs index d656dc39f6e3f..4e736f2b7ab65 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/Argument.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/Argument.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - namespace Microsoft.CodeAnalysis.GenerateMember.GenerateConstructor { internal abstract partial class AbstractGenerateConstructorService @@ -12,9 +10,9 @@ protected readonly struct Argument { public readonly RefKind RefKind; public readonly string Name; - public readonly TExpressionSyntax Expression; + public readonly TExpressionSyntax? Expression; - public Argument(RefKind refKind, string name, TExpressionSyntax expression) + public Argument(RefKind refKind, string? name, TExpressionSyntax? expression) { RefKind = refKind; Name = name ?? ""; diff --git a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/GenerateConstructorHelpers.cs b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/GenerateConstructorHelpers.cs index 1ad27300d325f..d2132e9b5536d 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/GenerateConstructorHelpers.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/GenerateConstructorHelpers.cs @@ -18,7 +18,7 @@ internal static class GenerateConstructorHelpers public static bool CanDelegateTo( SemanticDocument document, ImmutableArray parameters, - ImmutableArray expressions, + ImmutableArray expressions, IMethodSymbol constructor) where TExpressionSyntax : SyntaxNode { @@ -73,7 +73,7 @@ private static bool IsCompatible( ISemanticFactsService semanticFacts, SemanticModel semanticModel, IMethodSymbol constructor, - ImmutableArray expressions) + ImmutableArray expressions) where TExpressionSyntax : SyntaxNode { Debug.Assert(constructor.Parameters.Length == expressions.Length); @@ -107,6 +107,10 @@ private static bool IsCompatible( if (constructorParameter == null) return false; + // In VB the argument may not have been specified at all if the parameter is optional + if (expressions[i] is null && constructorParameter.IsOptional) + continue; + var conversion = semanticFacts.ClassifyConversion(semanticModel, expressions[i], constructorParameter.Type); if (!conversion.IsIdentity && !conversion.IsImplicit) return false; diff --git a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/IGenerateConstructorService.cs b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/IGenerateConstructorService.cs index 85711af0ce317..195870f7ba76b 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/IGenerateConstructorService.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/IGenerateConstructorService.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks;