AbstractGenerateConstructorService.Editor.cs 21.3 KB
Newer Older
1
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
2 3 4

using System;
using System.Collections.Generic;
C
CyrusNajmabadi 已提交
5
using System.Collections.Immutable;
6 7 8 9 10 11
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Editing;
T
Tomas Matousek 已提交
12
using Microsoft.CodeAnalysis.PooledObjects;
13 14
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
15
using Microsoft.CodeAnalysis.Utilities;
16 17 18 19 20 21 22 23 24 25 26 27
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.GenerateMember.GenerateConstructor
{
    internal abstract partial class AbstractGenerateConstructorService<TService, TArgumentSyntax, TAttributeArgumentSyntax>
    {
        protected abstract bool IsConversionImplicit(Compilation compilation, ITypeSymbol sourceType, ITypeSymbol targetType);

        internal abstract IMethodSymbol GetDelegatingConstructor(State state, SemanticDocument document, int argumentCount, INamedTypeSymbol namedType, ISet<IMethodSymbol> candidates, CancellationToken cancellationToken);

        private partial class Editor
        {
28 29 30
            private readonly TService _service;
            private readonly SemanticDocument _document;
            private readonly State _state;
31
            private readonly bool _withFields;
32
            private readonly CancellationToken _cancellationToken;
33 34 35 36 37

            public Editor(
                TService service,
                SemanticDocument document,
                State state,
38
                bool withFields,
39 40
                CancellationToken cancellationToken)
            {
41 42 43
                _service = service;
                _document = document;
                _state = state;
44
                _withFields = withFields;
45
                _cancellationToken = cancellationToken;
46 47
            }

48
            internal async Task<(Document, bool addedFields)> GetEditAsync()
49
            {
50
                // See if there's an accessible base constructor that would accept these
51 52 53 54 55
                // types, then just call into that instead of generating fields.
                //
                // then, see if there are any constructors that would take the first 'n' arguments
                // we've provided.  If so, delegate to those, and then create a field for any
                // remaining arguments.  Try to match from largest to smallest.
56 57 58 59 60 61
                var edit = await GenerateThisOrBaseDelegatingConstructorAsync().ConfigureAwait(false);
                if (edit.document != null)
                {
                    return edit;
                }

C
CyrusNajmabadi 已提交
62 63
                // Otherwise, just generate a normal constructor that assigns any provided
                // parameters into fields.
64
                return await GenerateFieldDelegatingConstructorAsync().ConfigureAwait(false);
65 66
            }

67
            private async Task<(Document document, bool addedFields)> GenerateThisOrBaseDelegatingConstructorAsync()
68 69 70
            {
                // We don't have to deal with the zero length case, since there's nothing to
                // delegate.  It will fall out of the GenerateFieldDelegatingConstructor above.
C
CyrusNajmabadi 已提交
71
                for (int i = _state.Arguments.Length; i >= 1; i--)
72
                {
C
CyrusNajmabadi 已提交
73 74
                    var edit = await GenerateThisOrBaseDelegatingConstructorAsync(i).ConfigureAwait(false);
                    if (edit.document != null)
75
                    {
C
CyrusNajmabadi 已提交
76
                        return edit;
77 78 79
                    }
                }

C
CyrusNajmabadi 已提交
80
                return default;
81 82
            }

C
CyrusNajmabadi 已提交
83
            private async Task<(Document document, bool addedFields)> GenerateThisOrBaseDelegatingConstructorAsync(int argumentCount)
84
            {
85 86 87
                (Document document, bool addedField) edit;
                if ((edit = await GenerateDelegatingConstructorAsync(argumentCount, _state.TypeToGenerateIn).ConfigureAwait(false)).document != null ||
                    (edit = await GenerateDelegatingConstructorAsync(argumentCount, _state.TypeToGenerateIn.BaseType).ConfigureAwait(false)).document != null)
88 89 90 91
                {
                    return edit;
                }

92
                return default;
93 94
            }

95
            private async Task<(Document, bool addedFields)> GenerateDelegatingConstructorAsync(
96 97 98 99 100
                int argumentCount,
                INamedTypeSymbol namedType)
            {
                if (namedType == null)
                {
101
                    return default;
102 103 104
                }

                // We can't resolve overloads across language.
105
                if (_document.Project.Language != namedType.Language)
106
                {
107
                    return default;
108 109
                }

110
                var arguments = _state.Arguments.Take(argumentCount).ToList();
C
CyrusNajmabadi 已提交
111 112 113 114 115
                var remainingArguments = _state.Arguments.Skip(argumentCount).ToImmutableArray();
                var remainingAttributeArguments = _state.AttributeArguments != null 
                    ? _state.AttributeArguments.Skip(argumentCount).ToImmutableArray() 
                    : (ImmutableArray<TAttributeArgumentSyntax>?)null;
                var remainingParameterTypes = _state.ParameterTypes.Skip(argumentCount).ToImmutableArray();
116 117 118 119

                var instanceConstructors = namedType.InstanceConstructors.Where(IsSymbolAccessible).ToSet();
                if (instanceConstructors.IsEmpty())
                {
120
                    return default;
121 122
                }

123
                var delegatedConstructor = _service.GetDelegatingConstructor(_state, _document, argumentCount, namedType, instanceConstructors, _cancellationToken);
124 125
                if (delegatedConstructor == null)
                {
126
                    return default;
127 128 129
                }

                // There was a best match.  Call it directly.  
130
                var provider = _document.Project.Solution.Workspace.Services.GetLanguageServices(_state.TypeToGenerateIn.Language);
131 132
                var syntaxFactory = provider.GetService<SyntaxGenerator>();
                var codeGenerationService = provider.GetService<ICodeGenerationService>();
133

134 135 136 137 138 139
                // Map the first N parameters to the other constructor in this type.  Then
                // try to map any further parameters to existing fields.  Finally, generate
                // new fields if no such parameters exist.

                // Find the names of the parameters that will follow the parameters we're
                // delegating.
140
                var remainingParameterNames = _service.GenerateParameterNames(
141
                    _document.SemanticModel, remainingArguments,
142 143
                    delegatedConstructor.Parameters.Select(p => p.Name).ToList(),
                    _cancellationToken);
144 145 146

                // Can't generate the constructor if the parameter names we're copying over forcibly
                // conflict with any names we generated.
147
                if (delegatedConstructor.Parameters.Select(p => p.Name)
148
                        .Intersect(remainingParameterNames.Select(n => n.BestNameForParameter)).Any())
149
                {
150
                    return default;
151
                }
152

153
                // Try to map those parameters to fields.
C
CyrusNajmabadi 已提交
154
                this.GetParameters(remainingArguments, remainingAttributeArguments,
155
                    remainingParameterTypes, remainingParameterNames,
C
CyrusNajmabadi 已提交
156
                    out var parameterToExistingFieldMap, out var parameterToNewFieldMap, out var remainingParameters);
157

158 159 160
                var fields = _withFields
                    ? syntaxFactory.CreateFieldsForParameters(remainingParameters, parameterToNewFieldMap)
                    : ImmutableArray<IFieldSymbol>.Empty;
161 162 163 164
                var assignStatements = syntaxFactory.CreateAssignmentStatements(
                    _document.SemanticModel.Compilation, remainingParameters, 
                    parameterToExistingFieldMap, parameterToNewFieldMap,
                    addNullChecks: false, preferThrowExpression: false);
165

C
CyrusNajmabadi 已提交
166
                var allParameters = delegatedConstructor.Parameters.Concat(remainingParameters);
167

168
                var isThis = namedType.Equals(_state.TypeToGenerateIn);
169
                var delegatingArguments = syntaxFactory.CreateArguments(delegatedConstructor.Parameters);
C
CyrusNajmabadi 已提交
170 171
                var baseConstructorArguments = isThis ? default : delegatingArguments;
                var thisConstructorArguments = isThis ? delegatingArguments : default;
172 173

                var constructor = CodeGenerationSymbolFactory.CreateConstructorSymbol(
C
CyrusNajmabadi 已提交
174
                    attributes: default,
175
                    accessibility: Accessibility.Public,
C
CyrusNajmabadi 已提交
176
                    modifiers: default,
177
                    typeName: _state.TypeToGenerateIn.Name,
178
                    parameters: allParameters,
179
                    statements: assignStatements,
180 181 182
                    baseConstructorArguments: baseConstructorArguments,
                    thisConstructorArguments: thisConstructorArguments);

183
                var members = fields.OfType<ISymbol>().Concat(constructor);
184
                var result = await codeGenerationService.AddMembersAsync(
185 186
                    _document.Project.Solution,
                    _state.TypeToGenerateIn,
187
                    members,
188 189
                    new CodeGenerationOptions(_state.Token.GetLocation()),
                    _cancellationToken)
190 191
                    .ConfigureAwait(false);

192
                return (result, fields.Length > 0);
193 194
            }

195
            private async Task<(Document, bool addedFields)> GenerateFieldDelegatingConstructorAsync()
196
            {
C
CyrusNajmabadi 已提交
197
                var arguments = _state.Arguments;
198
                var parameterTypes = _state.ParameterTypes;
199

C
CyrusNajmabadi 已提交
200
                var typeParametersNames = _state.TypeToGenerateIn.GetAllTypeParameters().Select(t => t.Name).ToImmutableArray();
201
                var parameterNames = GetParameterNames(arguments, typeParametersNames);
202

203
                GetParameters(arguments, _state.AttributeArguments, parameterTypes, parameterNames,
C
CyrusNajmabadi 已提交
204
                    out var parameterToExistingFieldMap, out var parameterToNewFieldMap, out var parameters);
205

206
                var provider = _document.Project.Solution.Workspace.Services.GetLanguageServices(_state.TypeToGenerateIn.Language);
207 208 209
                var syntaxFactory = provider.GetService<SyntaxGenerator>();
                var codeGenerationService = provider.GetService<ICodeGenerationService>();

210
                var syntaxTree = _document.SyntaxTree;
211
                var (fields, constructor) = syntaxFactory.CreateFieldDelegatingConstructor(
212 213 214 215 216 217
                    _document.SemanticModel.Compilation, 
                    _state.TypeToGenerateIn.Name, 
                    _state.TypeToGenerateIn, parameters,
                    parameterToExistingFieldMap, parameterToNewFieldMap, 
                    addNullChecks: false, preferThrowExpression: false, 
                    cancellationToken: _cancellationToken);
218 219

                var result = await codeGenerationService.AddMembersAsync(
220 221
                    _document.Project.Solution,
                    _state.TypeToGenerateIn,
C
CyrusNajmabadi 已提交
222
                    fields.Concat(constructor),
223 224
                    new CodeGenerationOptions(_state.Token.GetLocation()),
                    _cancellationToken)
225 226
                    .ConfigureAwait(false);

227
                return (result, fields.Length > 0);
228 229
            }

C
CyrusNajmabadi 已提交
230 231
            private ImmutableArray<ParameterName> GetParameterNames(
                ImmutableArray<TArgumentSyntax> arguments, ImmutableArray<string> typeParametersNames)
232
            {
233
                return _state.AttributeArguments != null
234 235
                    ? _service.GenerateParameterNames(_document.SemanticModel, _state.AttributeArguments, typeParametersNames, _cancellationToken)
                    : _service.GenerateParameterNames(_document.SemanticModel, arguments, typeParametersNames, _cancellationToken);
236 237
            }

238
            private void GetParameters(
C
CyrusNajmabadi 已提交
239 240 241 242
                ImmutableArray<TArgumentSyntax> arguments,
                ImmutableArray<TAttributeArgumentSyntax>? attributeArguments,
                ImmutableArray<ITypeSymbol> parameterTypes,
                ImmutableArray<ParameterName> parameterNames,
243 244
                out Dictionary<string, ISymbol> parameterToExistingFieldMap,
                out Dictionary<string, string> parameterToNewFieldMap,
C
CyrusNajmabadi 已提交
245
                out ImmutableArray<IParameterSymbol> parameters)
246 247 248
            {
                parameterToExistingFieldMap = new Dictionary<string, ISymbol>();
                parameterToNewFieldMap = new Dictionary<string, string>();
C
CyrusNajmabadi 已提交
249
                var result = ArrayBuilder<IParameterSymbol>.GetInstance();
250

C
CyrusNajmabadi 已提交
251
                for (var i = 0; i < parameterNames.Length; i++)
252 253 254
                {
                    // See if there's a matching field we can use.  First test in a case sensitive
                    // manner, then case insensitively.
C
CyrusNajmabadi 已提交
255
                    if (!TryFindMatchingField(
256
                            arguments, attributeArguments, parameterNames, parameterTypes, i, parameterToExistingFieldMap,
C
CyrusNajmabadi 已提交
257
                            parameterToNewFieldMap, caseSensitive: true, newParameterNames: out parameterNames))
258
                    {
259 260
                        if (!TryFindMatchingField(
                                arguments, attributeArguments, parameterNames, parameterTypes, i, parameterToExistingFieldMap,
C
CyrusNajmabadi 已提交
261
                                parameterToNewFieldMap, caseSensitive: false, newParameterNames: out parameterNames))
262
                        {
263
                            parameterToNewFieldMap[parameterNames[i].BestNameForParameter] =
264
                                parameterNames[i].NameBasedOnArgument;
265 266 267
                        }
                    }

C
CyrusNajmabadi 已提交
268
                    result.Add(CodeGenerationSymbolFactory.CreateParameterSymbol(
C
CyrusNajmabadi 已提交
269
                        attributes: default,
270
                        refKind: _service.GetRefKind(arguments[i]),
271 272
                        isParams: false,
                        type: parameterTypes[i],
273
                        name: parameterNames[i].BestNameForParameter));
274
                }
C
CyrusNajmabadi 已提交
275

276 277 278 279 280
                if (!_withFields)
                {
                    parameterToNewFieldMap.Clear();
                }

C
CyrusNajmabadi 已提交
281
                parameters = result.ToImmutableAndFree();
282 283 284
            }

            private bool TryFindMatchingField(
C
CyrusNajmabadi 已提交
285 286 287 288
                ImmutableArray<TArgumentSyntax> arguments,
                ImmutableArray<TAttributeArgumentSyntax>? attributeArguments,
                ImmutableArray<ParameterName> parameterNames,
                ImmutableArray<ITypeSymbol> parameterTypes,
289 290 291
                int index,
                Dictionary<string, ISymbol> parameterToExistingFieldMap,
                Dictionary<string, string> parameterToNewFieldMap,
C
CyrusNajmabadi 已提交
292 293
                bool caseSensitive,
                out ImmutableArray<ParameterName> newParameterNames)
294 295 296
            {
                var parameterName = parameterNames[index];
                var parameterType = parameterTypes[index];
297
                var isFixed = _service.IsNamedArgument(arguments[index]);
C
CyrusNajmabadi 已提交
298
                var newParameterNamesList = parameterNames.ToList();
299 300 301 302 303 304

                // For non-out parameters, see if there's already a field there with the same name.
                // If so, and it has a compatible type, then we can just assign to that field.
                // Otherwise, we'll need to choose a different name for this member so that it
                // doesn't conflict with something already in the type. First check the current type
                // for a matching field.  If so, defer to it.
C
Charles Stoner 已提交
305
                var comparison = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
306

307
                foreach (var type in _state.TypeToGenerateIn.GetBaseTypesAndThis())
308
                {
309
                    var ignoreAccessibility = type.Equals(_state.TypeToGenerateIn);
310
                    var symbol = type.GetMembers()
311
                                     .FirstOrDefault(s => s.Name.Equals(parameterName.NameBasedOnArgument, comparison));
312 313 314 315 316 317 318 319

                    if (symbol != null)
                    {
                        if (ignoreAccessibility || IsSymbolAccessible(symbol))
                        {
                            if (IsViableFieldOrProperty(parameterType, symbol))
                            {
                                // Ok!  We can just the existing field.  
320
                                parameterToExistingFieldMap[parameterName.BestNameForParameter] = symbol;
321 322 323 324 325 326 327
                            }
                            else
                            {
                                // Uh-oh.  Now we have a problem.  We can't assign this parameter to
                                // this field.  So we need to create a new field.  Find a name not in
                                // use so we can assign to that.  
                                var newFieldName = NameGenerator.EnsureUniqueness(
C
CyrusNajmabadi 已提交
328
                                    attributeArguments != null
329 330
                                        ? _service.GenerateNameForArgument(_document.SemanticModel, attributeArguments.Value[index], _cancellationToken)
                                        : _service.GenerateNameForArgument(_document.SemanticModel, arguments[index], _cancellationToken),
331 332 333 334 335 336
                                    GetUnavailableMemberNames().Concat(parameterToNewFieldMap.Values));

                                if (isFixed)
                                {
                                    // Can't change the parameter name, so map the existing parameter
                                    // name to the new field name.
337
                                    parameterToNewFieldMap[parameterName.NameBasedOnArgument] = newFieldName;
338 339 340 341
                                }
                                else
                                {
                                    // Can change the parameter name, so do so.
342
                                    var newParameterName = new ParameterName(newFieldName, isFixed: false);
C
CyrusNajmabadi 已提交
343
                                    newParameterNamesList[index] = newParameterName;
344
                                    parameterToNewFieldMap[newParameterName.BestNameForParameter] = newFieldName;
345 346 347
                                }
                            }

C
CyrusNajmabadi 已提交
348
                            newParameterNames = newParameterNamesList.ToImmutableArray();
349 350 351 352 353
                            return true;
                        }
                    }
                }

C
CyrusNajmabadi 已提交
354
                newParameterNames = newParameterNamesList.ToImmutableArray();
355 356 357 358 359
                return false;
            }

            private IEnumerable<string> GetUnavailableMemberNames()
            {
360 361
                return _state.TypeToGenerateIn.MemberNames.Concat(
                    from type in _state.TypeToGenerateIn.GetBaseTypes()
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
                    from member in type.GetMembers()
                    select member.Name);
            }

            private bool IsViableFieldOrProperty(
                ITypeSymbol parameterType,
                ISymbol symbol)
            {
                if (parameterType.Language != symbol.Language)
                {
                    return false;
                }

                if (symbol != null && !symbol.IsStatic)
                {
C
CyrusNajmabadi 已提交
377
                    if (symbol is IFieldSymbol field)
378 379 380
                    {
                        return
                            !field.IsConst &&
381
                            _service.IsConversionImplicit(_document.SemanticModel.Compilation, parameterType, field.Type);
382
                    }
C
CyrusNajmabadi 已提交
383
                    else if (symbol is IPropertySymbol property)
384 385
                    {
                        return
J
Jonathon Marolf 已提交
386
                            property.Parameters.Length == 0 &&
J
Jonathon Marolf 已提交
387
                            property.IsWritableInConstructor() &&
388
                            _service.IsConversionImplicit(_document.SemanticModel.Compilation, parameterType, property.Type);
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
                    }
                }

                return false;
            }

            private bool IsSymbolAccessible(
                ISymbol symbol)
            {
                if (symbol == null)
                {
                    return false;
                }

                if (symbol.Kind == SymbolKind.Property)
                {
                    if (!IsSymbolAccessible(((IPropertySymbol)symbol).SetMethod))
                    {
                        return false;
                    }
                }

                // Public and protected constructors are accessible.  Internal constructors are
                // accessible if we have friend access.  We can't call the normal accessibility
                // checkers since they will think that a protected constructor isn't accessible
                // (since we don't have the destination type that would have access to them yet).
                switch (symbol.DeclaredAccessibility)
                {
                    case Accessibility.ProtectedOrInternal:
                    case Accessibility.Protected:
                    case Accessibility.Public:
                        return true;
                    case Accessibility.ProtectedAndInternal:
                    case Accessibility.Internal:
423
                        return _document.SemanticModel.Compilation.Assembly.IsSameAssemblyOrHasFriendAccessTo(
424 425 426 427 428 429 430 431 432
                            symbol.ContainingAssembly);

                    default:
                        return false;
                }
            }
        }
    }
}