diff --git a/eng/Versions.props b/eng/Versions.props index 1afcd5e1d0dae..fae37428d1527 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -36,7 +36,7 @@ 17.0.487 5.0.0-alpha1.19409.1 5.0.0-preview.1.20112.8 - 17.0.5133-g7b8c8bd49d + 17.1.3 17.0.31723.112 16.5.0 - 3.3.2 + 3.3.3 2.0.0-rc2-61102-09 $(MicrosoftCodeAnalysisTestingVersion) $(MicrosoftCodeAnalysisTestingVersion) diff --git a/eng/config/globalconfigs/Common.globalconfig b/eng/config/globalconfigs/Common.globalconfig index 9be4ffd974176..7dc808dcb31fd 100644 --- a/eng/config/globalconfigs/Common.globalconfig +++ b/eng/config/globalconfigs/Common.globalconfig @@ -28,6 +28,10 @@ dotnet_diagnostic.RS1022.severity = none # RS1024: Compare symbols correctly dotnet_diagnostic.RS1024.severity = refactoring +# RS1034: Prefer 'IsKind' for checking syntax kinds +# Hold on https://github.com/dotnet/roslyn/pull/51823#pullrequestreview-612747491 +dotnet_diagnostic.RS1034.severity = none + dotnet_diagnostic.AD0001.severity = error dotnet_diagnostic.RS0001.severity = warning diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 7506cb89b7456..95f7b6758aec2 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1931,7 +1931,7 @@ internal void GenerateAnonymousFunctionConversionError(BindingDiagnosticBag diag } if (anonymousFunction.FunctionType is { } functionType && - functionType.GetValue() is null) + functionType.GetInternalDelegateType() is null) { var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; if (Conversions.IsValidFunctionTypeConversionTarget(targetType, ref discardedUseSiteInfo)) diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/BestTypeInferrer.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/BestTypeInferrer.cs index 89ebc123adfda..7fc47ca82f16f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/BestTypeInferrer.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/BestTypeInferrer.cs @@ -91,8 +91,9 @@ public static NullableFlowState GetNullableState(ArrayBuilder typ if (result is FunctionTypeSymbol functionType) { - inferredFromFunctionType = true; - return functionType.GetInternalDelegateType(); + result = functionType.GetInternalDelegateType(); + inferredFromFunctionType = result is { }; + return result; } inferredFromFunctionType = false; @@ -182,14 +183,18 @@ public static NullableFlowState GetNullableState(ArrayBuilder typ case 0: return null; case 1: - return types[0]; + return checkType(types[0]); } TypeSymbol? best = null; int bestIndex = -1; for (int i = 0; i < types.Count; i++) { - TypeSymbol type = types[i]; + TypeSymbol? type = checkType(types[i]); + if (type is null) + { + continue; + } if (best is null) { best = type; @@ -220,7 +225,11 @@ public static NullableFlowState GetNullableState(ArrayBuilder typ // that every type *before* best was also worse. for (int i = 0; i < bestIndex; i++) { - TypeSymbol type = types[i]; + TypeSymbol? type = checkType(types[i]); + if (type is null) + { + continue; + } TypeSymbol? better = Better(best, type, conversions, ref useSiteInfo); if (!best.Equals(better, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)) { @@ -229,6 +238,11 @@ public static NullableFlowState GetNullableState(ArrayBuilder typ } return best; + + static TypeSymbol? checkType(TypeSymbol type) => + type is FunctionTypeSymbol functionType && functionType.GetInternalDelegateType() is null ? + null : + type; } /// diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs index ee552683aa453..c5c81078d7683 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs @@ -2669,7 +2669,8 @@ private bool HasImplicitFunctionTypeConversion(FunctionTypeSymbol source, TypeSy return HasImplicitFunctionTypeToFunctionTypeConversion(source, destinationFunctionType, ref useSiteInfo); } - return IsValidFunctionTypeConversionTarget(destination, ref useSiteInfo); + return IsValidFunctionTypeConversionTarget(destination, ref useSiteInfo) && + source.GetInternalDelegateType() is { }; } internal bool IsValidFunctionTypeConversionTarget(TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo) @@ -2697,7 +2698,16 @@ internal bool IsValidFunctionTypeConversionTarget(TypeSymbol destination, ref Co private bool HasImplicitFunctionTypeToFunctionTypeConversion(FunctionTypeSymbol sourceType, FunctionTypeSymbol destinationType, ref CompoundUseSiteInfo useSiteInfo) { var sourceDelegate = sourceType.GetInternalDelegateType(); + if (sourceDelegate is null) + { + return false; + } + var destinationDelegate = destinationType.GetInternalDelegateType(); + if (destinationDelegate is null) + { + return false; + } // https://github.com/dotnet/roslyn/issues/55909: We're relying on the variance of // FunctionTypeSymbol.GetInternalDelegateType() which fails for synthesized @@ -2848,13 +2858,14 @@ private bool HasAnyBaseInterfaceConversion(TypeSymbol derivedType, TypeSymbol ba // * if the ith parameter of U is contravariant then either Si is exactly // equal to Ti, or there is an implicit reference conversion from Ti to Si. +#nullable enable private bool HasInterfaceVarianceConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo) { Debug.Assert((object)source != null); Debug.Assert((object)destination != null); - NamedTypeSymbol s = source as NamedTypeSymbol; - NamedTypeSymbol d = destination as NamedTypeSymbol; - if ((object)s == null || (object)d == null) + NamedTypeSymbol? s = source as NamedTypeSymbol; + NamedTypeSymbol? d = destination as NamedTypeSymbol; + if (s is null || d is null) { return false; } @@ -2871,9 +2882,9 @@ private bool HasDelegateVarianceConversion(TypeSymbol source, TypeSymbol destina { Debug.Assert((object)source != null); Debug.Assert((object)destination != null); - NamedTypeSymbol s = source as NamedTypeSymbol; - NamedTypeSymbol d = destination as NamedTypeSymbol; - if ((object)s == null || (object)d == null) + NamedTypeSymbol? s = source as NamedTypeSymbol; + NamedTypeSymbol? d = destination as NamedTypeSymbol; + if (s is null || d is null) { return false; } @@ -3150,7 +3161,6 @@ internal static bool HasImplicitPointerToVoidConversion(TypeSymbol source, TypeS return source.IsPointerOrFunctionPointer() && destination is PointerTypeSymbol { PointedAtType: { SpecialType: SpecialType.System_Void } }; } -#nullable enable internal bool HasImplicitPointerConversion(TypeSymbol? source, TypeSymbol? destination, ref CompoundUseSiteInfo useSiteInfo) { if (!(source is FunctionPointerTypeSymbol { Signature: { } sourceSig }) diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs index 3d6df87fe2654..41cd2ec1072b8 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs @@ -2664,9 +2664,12 @@ private static (TypeWithAnnotations Type, bool FromFunctionType) Fix( if (containsFunctionTypes(lower) && (containsNonFunctionTypes(lower) || containsNonFunctionTypes(exact) || containsNonFunctionTypes(upper))) { - lower = removeFunctionTypes(lower); + lower = removeTypes(lower, static type => isFunctionType(type, out _)); } + // Remove any function types with no delegate type. + lower = removeTypes(lower, static type => isFunctionType(type, out var functionType) && functionType.GetInternalDelegateType() is null); + // Optimization: if we have one exact bound then we need not add any // inexact bounds; we're just going to remove them anyway. @@ -2806,12 +2809,16 @@ static bool isExpressionType(TypeSymbol? type) return false; } - static HashSet? removeFunctionTypes(HashSet types) + static HashSet? removeTypes(HashSet? types, Func predicate) { + if (types is null) + { + return null; + } HashSet? updated = null; foreach (var type in types) { - if (!isFunctionType(type, out _)) + if (!predicate(type)) { updated ??= new HashSet(TypeWithAnnotations.EqualsComparer.ConsiderEverythingComparer); updated.Add(type); diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundExpressionExtensions.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundExpressionExtensions.cs index cf855070b5234..34d94060b1d9c 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundExpressionExtensions.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundExpressionExtensions.cs @@ -111,13 +111,12 @@ public static bool HasDynamicType(this BoundExpression node) public static FunctionTypeSymbol? GetFunctionType(this BoundExpression expr) { - var lazyType = expr switch + return expr switch { BoundMethodGroup methodGroup => methodGroup.FunctionType, UnboundLambda unboundLambda => unboundLambda.FunctionType, _ => null }; - return lazyType?.GetValue(); } public static bool MethodGroupReceiverIsDynamic(this BoundMethodGroup node) diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundMethodGroup.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundMethodGroup.cs index e9cd966be0d25..01b94d33dbbef 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundMethodGroup.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundMethodGroup.cs @@ -20,14 +20,14 @@ public BoundMethodGroup( BoundMethodGroupFlags flags, Binder binder, bool hasErrors = false) - : this(syntax, typeArgumentsOpt, name, methods, lookupResult.SingleSymbolOrDefault, lookupResult.Error, flags, functionType: GetLazyFunctionType(binder, syntax), receiverOpt, lookupResult.Kind, hasErrors) + : this(syntax, typeArgumentsOpt, name, methods, lookupResult.SingleSymbolOrDefault, lookupResult.Error, flags, functionType: GetFunctionType(binder, syntax), receiverOpt, lookupResult.Kind, hasErrors) { FunctionType?.SetExpression(this); } - private static FunctionTypeSymbol.Lazy? GetLazyFunctionType(Binder binder, SyntaxNode syntax) + private static FunctionTypeSymbol? GetFunctionType(Binder binder, SyntaxNode syntax) { - return FunctionTypeSymbol.Lazy.CreateIfFeatureEnabled(syntax, binder, static (binder, expr) => binder.GetMethodGroupDelegateType((BoundMethodGroup)expr)); + return FunctionTypeSymbol.CreateIfFeatureEnabled(syntax, binder, static (binder, expr) => binder.GetMethodGroupDelegateType((BoundMethodGroup)expr)); } public MemberAccessExpressionSyntax? MemberAccessExpressionSyntax diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index a41f9b11afcd0..8f5c584ea5641 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -1704,7 +1704,7 @@ - + @@ -2117,7 +2117,7 @@ - + diff --git a/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs b/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs index 20f4e77f084f9..91d12ba91a738 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs @@ -253,8 +253,8 @@ private static TypeWithAnnotations CalculateReturnType( var bestType = returns[0].expr.GetTypeOrFunctionType(); if (bestType is FunctionTypeSymbol functionType) { - inferredFromFunctionType = true; bestType = functionType.GetInternalDelegateType(); + inferredFromFunctionType = bestType is { }; } else { @@ -390,7 +390,7 @@ public static UnboundLambda Create( Debug.Assert(syntax.IsAnonymousFunction()); bool hasErrors = !types.IsDefault && types.Any(t => t.Type?.Kind == SymbolKind.ErrorType); - var functionType = FunctionTypeSymbol.Lazy.CreateIfFeatureEnabled(syntax, binder, static (binder, expr) => ((UnboundLambda)expr).Data.InferDelegateType()); + var functionType = FunctionTypeSymbol.CreateIfFeatureEnabled(syntax, binder, static (binder, expr) => ((UnboundLambda)expr).Data.InferDelegateType()); var data = new PlainUnboundLambdaState(binder, returnRefKind, returnType, parameterAttributes, names, discardsOpt, types, refKinds, isAsync, isStatic, includeCache: true); var lambda = new UnboundLambda(syntax, data, functionType, withDependencies, hasErrors: hasErrors); data.SetUnboundLambda(lambda); @@ -398,7 +398,7 @@ public static UnboundLambda Create( return lambda; } - private UnboundLambda(SyntaxNode syntax, UnboundLambdaState state, FunctionTypeSymbol.Lazy? functionType, bool withDependencies, NullableWalker.VariableState? nullableState, bool hasErrors) : + private UnboundLambda(SyntaxNode syntax, UnboundLambdaState state, FunctionTypeSymbol? functionType, bool withDependencies, NullableWalker.VariableState? nullableState, bool hasErrors) : this(syntax, state, functionType, withDependencies, hasErrors) { this._nullableState = nullableState; diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index 04818f305e709..b8bf3ced13628 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -140,23 +140,11 @@ internal Conversions Conversions private ImmutableHashSet? _usageOfUsingsRecordedInTrees = ImmutableHashSet.Empty; /// - /// Nullable analysis data for methods, parameter default values, and attributes. - /// The key is a symbol for methods or parameters, and syntax for attributes. - /// The data is collected during testing only. + /// Optional data collected during testing only. + /// Used for instance for nullable analysis () + /// and inferred delegate types (). /// - internal NullableData? NullableAnalysisData; - - internal sealed class NullableData - { - internal readonly int MaxRecursionDepth; - internal readonly ConcurrentDictionary Data; - - internal NullableData(int maxRecursionDepth = -1) - { - MaxRecursionDepth = maxRecursionDepth; - Data = new ConcurrentDictionary(); - } - } + internal object? TestOnlyCompilationData; internal ImmutableHashSet? UsageOfUsingsRecordedInTrees => Volatile.Read(ref _usageOfUsingsRecordedInTrees); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 1cf58e3364a98..73f33bac5e64f 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -24,6 +25,23 @@ namespace Microsoft.CodeAnalysis.CSharp internal sealed partial class NullableWalker : LocalDataFlowPass { + /// + /// Nullable analysis data for methods, parameter default values, and attributes + /// stored on the Compilation during testing only. + /// The key is a symbol for methods or parameters, and syntax for attributes. + /// + internal sealed class NullableAnalysisData + { + internal readonly int MaxRecursionDepth; + internal readonly ConcurrentDictionary Data; + + internal NullableAnalysisData(int maxRecursionDepth = -1) + { + MaxRecursionDepth = maxRecursionDepth; + Data = new ConcurrentDictionary(); + } + } + /// /// Used to copy variable slots and types from the NullableWalker for the containing method /// or lambda to the NullableWalker created for a nested lambda or local function. @@ -428,7 +446,7 @@ public string GetDebuggerDisplay() protected override void EnsureSufficientExecutionStack(int recursionDepth) { if (recursionDepth > StackGuard.MaxUncheckedRecursionDepth && - compilation.NullableAnalysisData is { MaxRecursionDepth: var depth } && + compilation.TestOnlyCompilationData is NullableAnalysisData { MaxRecursionDepth: var depth } && depth > 0 && recursionDepth > depth) { @@ -1511,7 +1529,7 @@ private static void Analyze( private void RecordNullableAnalysisData(Symbol? symbol, bool requiredAnalysis) { - if (compilation.NullableAnalysisData?.Data is { } state) + if (compilation.TestOnlyCompilationData is NullableAnalysisData { Data: { } state }) { var key = (object?)symbol ?? methodMainNode.Syntax; if (state.TryGetValue(key, out var result)) @@ -1690,9 +1708,11 @@ protected override int MakeSlot(BoundExpression node) TypeSymbol? nodeType = node.Type; var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; var conversionsWithoutNullability = this.compilation.Conversions; - Debug.Assert(node.HasErrors || nodeType!.IsErrorType() || - conversionsWithoutNullability.HasIdentityOrImplicitReferenceConversion(slotType, nodeType, ref discardedUseSiteInfo) || - conversionsWithoutNullability.HasBoxingConversion(slotType, nodeType, ref discardedUseSiteInfo)); + Debug.Assert(node.HasErrors || + (nodeType is { } && + (nodeType.IsErrorType() || + conversionsWithoutNullability.HasIdentityOrImplicitReferenceConversion(slotType, nodeType, ref discardedUseSiteInfo) || + conversionsWithoutNullability.HasBoxingConversion(slotType, nodeType, ref discardedUseSiteInfo)))); } #endif return result; diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index f0c251aa2dcf0..1c25197106e26 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -6024,7 +6024,7 @@ public BoundComplexConditionalReceiver Update(BoundExpression valueTypeReceiver, internal sealed partial class BoundMethodGroup : BoundMethodOrPropertyGroup { - public BoundMethodGroup(SyntaxNode syntax, ImmutableArray typeArgumentsOpt, string name, ImmutableArray methods, Symbol? lookupSymbolOpt, DiagnosticInfo? lookupError, BoundMethodGroupFlags? flags, FunctionTypeSymbol.Lazy? functionType, BoundExpression? receiverOpt, LookupResultKind resultKind, bool hasErrors = false) + public BoundMethodGroup(SyntaxNode syntax, ImmutableArray typeArgumentsOpt, string name, ImmutableArray methods, Symbol? lookupSymbolOpt, DiagnosticInfo? lookupError, BoundMethodGroupFlags? flags, FunctionTypeSymbol? functionType, BoundExpression? receiverOpt, LookupResultKind resultKind, bool hasErrors = false) : base(BoundKind.MethodGroup, syntax, receiverOpt, resultKind, hasErrors || receiverOpt.HasErrors()) { @@ -6053,13 +6053,13 @@ public BoundMethodGroup(SyntaxNode syntax, ImmutableArray t public BoundMethodGroupFlags? Flags { get; } - public FunctionTypeSymbol.Lazy? FunctionType { get; } + public FunctionTypeSymbol? FunctionType { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitMethodGroup(this); - public BoundMethodGroup Update(ImmutableArray typeArgumentsOpt, string name, ImmutableArray methods, Symbol? lookupSymbolOpt, DiagnosticInfo? lookupError, BoundMethodGroupFlags? flags, FunctionTypeSymbol.Lazy? functionType, BoundExpression? receiverOpt, LookupResultKind resultKind) + public BoundMethodGroup Update(ImmutableArray typeArgumentsOpt, string name, ImmutableArray methods, Symbol? lookupSymbolOpt, DiagnosticInfo? lookupError, BoundMethodGroupFlags? flags, FunctionTypeSymbol? functionType, BoundExpression? receiverOpt, LookupResultKind resultKind) { - if (typeArgumentsOpt != this.TypeArgumentsOpt || name != this.Name || methods != this.Methods || !Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(lookupSymbolOpt, this.LookupSymbolOpt) || lookupError != this.LookupError || flags != this.Flags || functionType != this.FunctionType || receiverOpt != this.ReceiverOpt || resultKind != this.ResultKind) + if (typeArgumentsOpt != this.TypeArgumentsOpt || name != this.Name || methods != this.Methods || !Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(lookupSymbolOpt, this.LookupSymbolOpt) || lookupError != this.LookupError || flags != this.Flags || !Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(functionType, this.FunctionType) || receiverOpt != this.ReceiverOpt || resultKind != this.ResultKind) { var result = new BoundMethodGroup(this.Syntax, typeArgumentsOpt, name, methods, lookupSymbolOpt, lookupError, flags, functionType, receiverOpt, resultKind, this.HasErrors); result.CopyAttributes(this); @@ -7480,7 +7480,7 @@ public BoundLambda Update(UnboundLambda unboundLambda, LambdaSymbol symbol, Boun internal sealed partial class UnboundLambda : BoundExpression { - public UnboundLambda(SyntaxNode syntax, UnboundLambdaState data, FunctionTypeSymbol.Lazy? functionType, Boolean withDependencies, bool hasErrors) + public UnboundLambda(SyntaxNode syntax, UnboundLambdaState data, FunctionTypeSymbol? functionType, Boolean withDependencies, bool hasErrors) : base(BoundKind.UnboundLambda, syntax, null, hasErrors) { @@ -7491,7 +7491,7 @@ public UnboundLambda(SyntaxNode syntax, UnboundLambdaState data, FunctionTypeSym this.WithDependencies = withDependencies; } - public UnboundLambda(SyntaxNode syntax, UnboundLambdaState data, FunctionTypeSymbol.Lazy? functionType, Boolean withDependencies) + public UnboundLambda(SyntaxNode syntax, UnboundLambdaState data, FunctionTypeSymbol? functionType, Boolean withDependencies) : base(BoundKind.UnboundLambda, syntax, null) { @@ -7507,15 +7507,15 @@ public UnboundLambda(SyntaxNode syntax, UnboundLambdaState data, FunctionTypeSym public UnboundLambdaState Data { get; } - public FunctionTypeSymbol.Lazy? FunctionType { get; } + public FunctionTypeSymbol? FunctionType { get; } public Boolean WithDependencies { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitUnboundLambda(this); - public UnboundLambda Update(UnboundLambdaState data, FunctionTypeSymbol.Lazy? functionType, Boolean withDependencies) + public UnboundLambda Update(UnboundLambdaState data, FunctionTypeSymbol? functionType, Boolean withDependencies) { - if (data != this.Data || functionType != this.FunctionType || withDependencies != this.WithDependencies) + if (data != this.Data || !Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(functionType, this.FunctionType) || withDependencies != this.WithDependencies) { var result = new UnboundLambda(this.Syntax, data, functionType, withDependencies, this.HasErrors); result.CopyAttributes(this); @@ -13482,17 +13482,18 @@ public NullabilityRewriter(ImmutableDictionary methods = GetUpdatedArray(node, node.Methods); Symbol? lookupSymbolOpt = GetUpdatedSymbol(node, node.LookupSymbolOpt); + FunctionTypeSymbol? functionType = GetUpdatedSymbol(node, node.FunctionType); BoundExpression? receiverOpt = (BoundExpression?)this.Visit(node.ReceiverOpt); BoundMethodGroup updatedNode; if (_updatedNullabilities.TryGetValue(node, out (NullabilityInfo Info, TypeSymbol? Type) infoAndType)) { - updatedNode = node.Update(node.TypeArgumentsOpt, node.Name, methods, lookupSymbolOpt, node.LookupError, node.Flags, node.FunctionType, receiverOpt, node.ResultKind); + updatedNode = node.Update(node.TypeArgumentsOpt, node.Name, methods, lookupSymbolOpt, node.LookupError, node.Flags, functionType, receiverOpt, node.ResultKind); updatedNode.TopLevelNullability = infoAndType.Info; } else { - updatedNode = node.Update(node.TypeArgumentsOpt, node.Name, methods, lookupSymbolOpt, node.LookupError, node.Flags, node.FunctionType, receiverOpt, node.ResultKind); + updatedNode = node.Update(node.TypeArgumentsOpt, node.Name, methods, lookupSymbolOpt, node.LookupError, node.Flags, functionType, receiverOpt, node.ResultKind); } return updatedNode; } @@ -14078,13 +14079,18 @@ public NullabilityRewriter(ImmutableDictionary - /// A lazily calculated instance of that represents - /// the inferred signature of a lambda expression or method group. - /// The actual signature is calculated on demand in . - /// - internal sealed class Lazy - { - private readonly Binder _binder; - private readonly Func _calculateDelegate; - - private FunctionTypeSymbol? _lazyFunctionType; - private BoundExpression? _expression; - - internal static Lazy? CreateIfFeatureEnabled(SyntaxNode syntax, Binder binder, Func calculateDelegate) - { - return syntax.IsFeatureEnabled(MessageID.IDS_FeatureInferredDelegateType) ? - new Lazy(binder, calculateDelegate) : - null; - } - - private Lazy(Binder binder, Func calculateDelegate) - { - _binder = binder; - _calculateDelegate = calculateDelegate; - _lazyFunctionType = FunctionTypeSymbol.Uninitialized; - } - - internal void SetExpression(BoundExpression expression) - { - Debug.Assert((object?)_lazyFunctionType == FunctionTypeSymbol.Uninitialized); - Debug.Assert(_expression is null); - Debug.Assert(expression.Kind is BoundKind.MethodGroup or BoundKind.UnboundLambda); - - _expression = expression; - } - - /// - /// Returns the inferred signature as a - /// or null if the signature could not be inferred. - /// - internal FunctionTypeSymbol? GetValue() - { - Debug.Assert(_expression is { }); - - if ((object?)_lazyFunctionType == FunctionTypeSymbol.Uninitialized) - { - var delegateType = _calculateDelegate(_binder, _expression); - var functionType = delegateType is null ? null : new FunctionTypeSymbol(delegateType); - Interlocked.CompareExchange(ref _lazyFunctionType, functionType, FunctionTypeSymbol.Uninitialized); - } - - return _lazyFunctionType; - } - } - } -} diff --git a/src/Compilers/CSharp/Portable/Symbols/FunctionTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/FunctionTypeSymbol.cs index f50feaa6f7ea1..06411db4b99c8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/FunctionTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/FunctionTypeSymbol.cs @@ -6,31 +6,94 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Threading; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { /// - /// A implementation that represents the inferred signature of a + /// Inferred delegate type state, recorded during testing only. + /// + internal sealed class InferredDelegateTypeData + { + /// + /// Number of delegate types calculated in the compilation. + /// + internal int InferredDelegateCount; + } + + /// + /// A implementation that represents the lazily-inferred signature of a /// lambda expression or method group. This is implemented as a /// to allow types and function signatures to be treated similarly in , /// , and . Instances of this type /// should only be used in those code paths and should not be exposed from the symbol model. + /// The actual delegate signature is calculated on demand in . /// [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] - internal sealed partial class FunctionTypeSymbol : TypeSymbol + internal sealed class FunctionTypeSymbol : TypeSymbol { - internal static readonly FunctionTypeSymbol Uninitialized = new FunctionTypeSymbol(ErrorTypeSymbol.UnknownResultType); + private static readonly NamedTypeSymbol Uninitialized = new UnsupportedMetadataTypeSymbol(); + + private readonly Binder? _binder; + private readonly Func? _calculateDelegate; + + private BoundExpression? _expression; + private NamedTypeSymbol? _lazyDelegateType; + + internal static FunctionTypeSymbol? CreateIfFeatureEnabled(SyntaxNode syntax, Binder binder, Func calculateDelegate) + { + return syntax.IsFeatureEnabled(MessageID.IDS_FeatureInferredDelegateType) ? + new FunctionTypeSymbol(binder, calculateDelegate) : + null; + } - private readonly NamedTypeSymbol _delegateType; + private FunctionTypeSymbol(Binder binder, Func calculateDelegate) + { + _binder = binder; + _calculateDelegate = calculateDelegate; + _lazyDelegateType = Uninitialized; + } internal FunctionTypeSymbol(NamedTypeSymbol delegateType) { - _delegateType = delegateType; + _lazyDelegateType = delegateType; } - internal NamedTypeSymbol GetInternalDelegateType() => _delegateType; + internal void SetExpression(BoundExpression expression) + { + Debug.Assert((object?)_lazyDelegateType == Uninitialized); + Debug.Assert(_expression is null); + Debug.Assert(expression.Kind is BoundKind.MethodGroup or BoundKind.UnboundLambda); + + _expression = expression; + } + + /// + /// Returns the inferred signature as a delegate type + /// or null if the signature could not be inferred. + /// + internal NamedTypeSymbol? GetInternalDelegateType() + { + if ((object?)_lazyDelegateType == Uninitialized) + { + Debug.Assert(_binder is { }); + Debug.Assert(_calculateDelegate is { }); + Debug.Assert(_expression is { }); + + var delegateType = _calculateDelegate(_binder, _expression); + var result = Interlocked.CompareExchange(ref _lazyDelegateType, delegateType, Uninitialized); + + if (_binder.Compilation.TestOnlyCompilationData is InferredDelegateTypeData data && + (object?)result == Uninitialized) + { + Interlocked.Increment(ref data.InferredDelegateCount); + } + } + + return _lazyDelegateType; + } public override bool IsReferenceType => true; @@ -98,22 +161,35 @@ internal override TypeSymbol MergeEquivalentTypes(TypeSymbol other, VarianceKind { Debug.Assert(this.Equals(other, TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)); + var thisDelegateType = GetInternalDelegateType(); var otherType = (FunctionTypeSymbol)other; - var delegateType = (NamedTypeSymbol)_delegateType.MergeEquivalentTypes(otherType._delegateType, variance); + var otherDelegateType = otherType.GetInternalDelegateType(); + + if (thisDelegateType is null || otherDelegateType is null) + { + return this; + } - return (object)_delegateType == delegateType ? + var mergedDelegateType = (NamedTypeSymbol)thisDelegateType.MergeEquivalentTypes(otherDelegateType, variance); + return (object)thisDelegateType == mergedDelegateType ? this : - otherType.WithDelegateType(delegateType); + otherType.WithDelegateType(mergedDelegateType); } internal override TypeSymbol SetNullabilityForReferenceTypes(Func transform) { - return WithDelegateType((NamedTypeSymbol)_delegateType.SetNullabilityForReferenceTypes(transform)); + var thisDelegateType = GetInternalDelegateType(); + if (thisDelegateType is null) + { + return this; + } + return WithDelegateType((NamedTypeSymbol)thisDelegateType.SetNullabilityForReferenceTypes(transform)); } private FunctionTypeSymbol WithDelegateType(NamedTypeSymbol delegateType) { - return (object)_delegateType == delegateType ? + var thisDelegateType = GetInternalDelegateType(); + return (object?)thisDelegateType == delegateType ? this : new FunctionTypeSymbol(delegateType); } @@ -127,18 +203,30 @@ internal override bool Equals(TypeSymbol t2, TypeCompareKind compareKind) return true; } - var otherType = (t2 as FunctionTypeSymbol)?._delegateType; - return _delegateType.Equals(otherType, compareKind); + if (t2 is FunctionTypeSymbol otherType) + { + var thisDelegateType = GetInternalDelegateType(); + var otherDelegateType = otherType.GetInternalDelegateType(); + + if (thisDelegateType is null || otherDelegateType is null) + { + return false; + } + + return Equals(thisDelegateType, otherDelegateType, compareKind); + } + + return false; } public override int GetHashCode() { - return _delegateType.GetHashCode(); + return GetInternalDelegateType()?.GetHashCode() ?? 0; } internal override string GetDebuggerDisplay() { - return $"FunctionTypeSymbol: {_delegateType.GetDebuggerDisplay()}"; + return $"FunctionTypeSymbol: {GetInternalDelegateType()?.GetDebuggerDisplay()}"; } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs index 3f8dae9399887..96d2962bd7362 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs @@ -2317,7 +2317,8 @@ internal bool Equals(TypeWithAnnotations other) throw ExceptionUtilities.Unreachable; } - public static bool Equals(TypeSymbol left, TypeSymbol right, TypeCompareKind comparison) +#nullable enable + public static bool Equals(TypeSymbol? left, TypeSymbol? right, TypeCompareKind comparison) { if (left is null) { @@ -2326,6 +2327,7 @@ public static bool Equals(TypeSymbol left, TypeSymbol right, TypeCompareKind com return left.Equals(right, comparison); } +#nullable disable [Obsolete("Use 'TypeSymbol.Equals(TypeSymbol, TypeSymbol, TypeCompareKind)' method.", true)] public static bool operator ==(TypeSymbol left, TypeSymbol right) diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index 653377a7045f3..7e18c0095c956 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -10016,6 +10016,186 @@ class C void RunWithTwoGenerators() => VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/langversion:preview", "/features:enable-generator-cache" }, generators: new[] { generator.AsSourceGenerator(), generator2.AsSourceGenerator() }, driverCache: cache, analyzers: null); } + [Fact] + public void Compiler_Updates_Cached_Driver_AdditionalTexts() + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText("class C { }"); + var additionalFile = dir.CreateFile("additionalFile.txt").WriteAllText("some text"); + + int sourceCallbackCount = 0; + int additionalFileCallbackCount = 0; + var generator = new PipelineCallbackGenerator((ctx) => + { + ctx.RegisterSourceOutput(ctx.ParseOptionsProvider, (spc, po) => + { + sourceCallbackCount++; + }); + + ctx.RegisterSourceOutput(ctx.AdditionalTextsProvider, (spc, po) => + { + additionalFileCallbackCount++; + }); + + }); + + GeneratorDriverCache cache = new GeneratorDriverCache(); + + RunWithCache(); + Assert.Equal(1, sourceCallbackCount); + Assert.Equal(1, additionalFileCallbackCount); + + RunWithCache(); + Assert.Equal(1, sourceCallbackCount); + Assert.Equal(1, additionalFileCallbackCount); + + additionalFile.WriteAllText("some new content"); + + RunWithCache(); + Assert.Equal(1, sourceCallbackCount); + Assert.Equal(2, additionalFileCallbackCount); // additional file was updated + + // Clean up temp files + CleanupAllGeneratedFiles(src.Path); + Directory.Delete(dir.Path, true); + + void RunWithCache() => VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/langversion:preview", "/features:enable-generator-cache", "/additionalFile:" + additionalFile.Path }, generators: new[] { generator.AsSourceGenerator() }, driverCache: cache, analyzers: null); + } + + [Fact] + public void Compiler_DoesNotCache_Driver_ConfigProvider() + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText("class C { }"); + var editorconfig = dir.CreateFile(".editorconfig").WriteAllText(@" +[temp.cs] +a = localA +"); + + var globalconfig = dir.CreateFile(".globalconfig").WriteAllText(@" +is_global = true +a = globalA"); + + int sourceCallbackCount = 0; + int configOptionsCallbackCount = 0; + int filteredGlobalCallbackCount = 0; + int filteredLocalCallbackCount = 0; + string globalA = ""; + string localA = ""; + var generator = new PipelineCallbackGenerator((ctx) => + { + ctx.RegisterSourceOutput(ctx.ParseOptionsProvider, (spc, po) => + { + sourceCallbackCount++; + }); + + ctx.RegisterSourceOutput(ctx.AnalyzerConfigOptionsProvider, (spc, po) => + { + configOptionsCallbackCount++; + po.GlobalOptions.TryGetValue("a", out globalA); + }); + + ctx.RegisterSourceOutput(ctx.AnalyzerConfigOptionsProvider.Select((p, _) => { p.GlobalOptions.TryGetValue("a", out var value); return value; }), (spc, value) => + { + filteredGlobalCallbackCount++; + globalA = value; + }); + + var syntaxTreeInput = ctx.CompilationProvider.Select((c, _) => c.SyntaxTrees.First()); + ctx.RegisterSourceOutput(ctx.AnalyzerConfigOptionsProvider.Combine(syntaxTreeInput).Select((p, _) => { p.Left.GetOptions(p.Right).TryGetValue("a", out var value); return value; }), (spc, value) => + { + filteredLocalCallbackCount++; + localA = value; + }); + }); + + GeneratorDriverCache cache = new GeneratorDriverCache(); + + RunWithCache(); + Assert.Equal(1, sourceCallbackCount); + Assert.Equal(1, configOptionsCallbackCount); + Assert.Equal(1, filteredGlobalCallbackCount); + Assert.Equal(1, filteredLocalCallbackCount); + Assert.Equal("globalA", globalA); + Assert.Equal("localA", localA); + + + RunWithCache(); + Assert.Equal(1, sourceCallbackCount); + Assert.Equal(2, configOptionsCallbackCount); // we can't compare the provider directly, so we consider it modified + Assert.Equal(1, filteredGlobalCallbackCount); // however, the values in it will cache out correctly. + Assert.Equal(1, filteredLocalCallbackCount); + + editorconfig.WriteAllText(@" +[temp.cs] +a = diffLocalA +"); + + RunWithCache(); + Assert.Equal(1, sourceCallbackCount); + Assert.Equal(3, configOptionsCallbackCount); + Assert.Equal(1, filteredGlobalCallbackCount); // the provider changed, but only the local value changed + Assert.Equal(2, filteredLocalCallbackCount); + Assert.Equal("globalA", globalA); + Assert.Equal("diffLocalA", localA); + + + globalconfig.WriteAllText(@" +is_global = true +a = diffGlobalA +"); + + RunWithCache(); + Assert.Equal(1, sourceCallbackCount); + Assert.Equal(4, configOptionsCallbackCount); + Assert.Equal(2, filteredGlobalCallbackCount); // only the global value was changed + Assert.Equal(2, filteredLocalCallbackCount); + Assert.Equal("diffGlobalA", globalA); + Assert.Equal("diffLocalA", localA); + + // Clean up temp files + CleanupAllGeneratedFiles(src.Path); + Directory.Delete(dir.Path, true); + + void RunWithCache() => VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/langversion:preview", "/features:enable-generator-cache", "/analyzerConfig:" + editorconfig.Path, "/analyzerConfig:" + globalconfig.Path }, generators: new[] { generator.AsSourceGenerator() }, driverCache: cache, analyzers: null); + } + + [Fact] + public void Compiler_DoesNotCache_Compilation() + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(@" +class C +{ +}"); + int sourceCallbackCount = 0; + var generator = new PipelineCallbackGenerator((ctx) => + { + ctx.RegisterSourceOutput(ctx.CompilationProvider, (spc, po) => + { + sourceCallbackCount++; + }); + }); + + // now re-run with a cache + GeneratorDriverCache cache = new GeneratorDriverCache(); + + RunWithCache(); + Assert.Equal(1, sourceCallbackCount); + + RunWithCache(); + Assert.Equal(2, sourceCallbackCount); + + RunWithCache(); + Assert.Equal(3, sourceCallbackCount); + + // Clean up temp files + CleanupAllGeneratedFiles(src.Path); + Directory.Delete(dir.Path, true); + + void RunWithCache() => VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/langversion:preview", "/features:enable-generator-cache" }, generators: new[] { generator.AsSourceGenerator() }, driverCache: cache, analyzers: null); + } + private static int OccurrenceCount(string source, string word) { var n = 0; @@ -14011,6 +14191,76 @@ public void Initialize(GeneratorInitializationContext context) {{ }} return generatorPath; } } + + [Theory] + [InlineData("a.txt", "b.txt", 2)] + [InlineData("a.txt", "a.txt", 1)] + [InlineData("abc/a.txt", "def/a.txt", 2)] + [InlineData("abc/a.txt", "abc/a.txt", 1)] + [InlineData("abc/a.txt", "abc/../a.txt", 2)] + [InlineData("abc/a.txt", "abc/./a.txt", 1)] + [InlineData("abc/a.txt", "abc/../abc/a.txt", 1)] + [InlineData("abc/a.txt", "abc/.././abc/a.txt", 1)] + [InlineData("abc/a.txt", "./abc/a.txt", 1)] + [InlineData("abc/a.txt", "../abc/../abc/a.txt", 2)] + [InlineData("abc/a.txt", "./abc/../abc/a.txt", 1)] + [InlineData("../abc/a.txt", "../abc/../abc/a.txt", 1)] + [InlineData("../abc/a.txt", "../abc/a.txt", 1)] + [InlineData("./abc/a.txt", "abc/a.txt", 1)] + public void TestDuplicateAdditionalFiles(string additionalFilePath1, string additionalFilePath2, int expectedCount) + { + var srcDirectory = Temp.CreateDirectory(); + var srcFile = srcDirectory.CreateFile("a.cs").WriteAllText("class C { }"); + + // make sure any parent or sub dirs exist too + Directory.CreateDirectory(Path.GetDirectoryName(Path.Combine(srcDirectory.Path, additionalFilePath1))); + Directory.CreateDirectory(Path.GetDirectoryName(Path.Combine(srcDirectory.Path, additionalFilePath2))); + + var additionalFile1 = srcDirectory.CreateFile(additionalFilePath1); + var additionalFile2 = expectedCount == 2 ? srcDirectory.CreateFile(additionalFilePath2) : null; + + string path1 = additionalFile1.Path; + string path2 = additionalFile2?.Path ?? Path.Combine(srcDirectory.Path, additionalFilePath2); + + int count = 0; + var generator = new PipelineCallbackGenerator(ctx => + { + ctx.RegisterSourceOutput(ctx.AdditionalTextsProvider, (spc, t) => + { + count++; + }); + }); + + var output = VerifyOutput(srcDirectory, srcFile, includeCurrentAssemblyAsAnalyzerReference: false, + additionalFlags: new[] { "/additionalfile:" + path1, "/additionalfile:" + path2 }, + generators: new[] { generator.AsSourceGenerator() }); + + Assert.Equal(expectedCount, count); + + CleanupAllGeneratedFiles(srcDirectory.Path); + } + + [ConditionalTheory(typeof(WindowsOnly))] + [InlineData("abc/a.txt", "abc\\a.txt", 1)] + [InlineData("abc\\a.txt", "abc\\a.txt", 1)] + [InlineData("abc/a.txt", "abc\\..\\a.txt", 2)] + [InlineData("abc/a.txt", "abc\\..\\abc\\a.txt", 1)] + [InlineData("abc/a.txt", "../abc\\../abc\\a.txt", 2)] + [InlineData("abc/a.txt", "./abc\\../abc\\a.txt", 1)] + [InlineData("../abc/a.txt", "../abc\\../abc\\a.txt", 1)] + [InlineData("a.txt", "A.txt", 1)] + [InlineData("abc/a.txt", "ABC\\a.txt", 1)] + [InlineData("abc/a.txt", "ABC\\A.txt", 1)] + public void TestDuplicateAdditionalFiles_Windows(string additionalFilePath1, string additionalFilePath2, int expectedCount) => TestDuplicateAdditionalFiles(additionalFilePath1, additionalFilePath2, expectedCount); + + [ConditionalTheory(typeof(LinuxOnly))] + [InlineData("a.txt", "A.txt", 2)] + [InlineData("abc/a.txt", "abc/A.txt", 2)] + [InlineData("abc/a.txt", "ABC/a.txt", 2)] + [InlineData("abc/a.txt", "./../abc/A.txt", 2)] + [InlineData("abc/a.txt", "./../ABC/a.txt", 2)] + public void TestDuplicateAdditionalFiles_Linux(string additionalFilePath1, string additionalFilePath2, int expectedCount) => TestDuplicateAdditionalFiles(additionalFilePath1, additionalFilePath2, expectedCount); + } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs index f50ef99d07f28..3db35f37d6dfc 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -9629,6 +9630,191 @@ static void Main() comp.VerifyDiagnostics(expectedDiagnostics); } + /// + /// Overload resolution and method type inference should not need to infer delegate + /// types for lambdas and method groups when the overloads have specific delegate types. + /// It is important to avoid inferring delegate types unnecessarily in these cases because + /// that would add overhead (particularly for overload resolution with nested lambdas) + /// while adding explicit parameter types for lambda expressions should ideally improve + /// overload resolution performance in those cases because fewer overloads may be applicable. + /// + [Fact] + [WorkItem(1153265, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1153265")] + [WorkItem(58106, "https://github.com/dotnet/roslyn/issues/58106")] + public void InferDelegateType_01() + { + var source = +@"using System; +class Program +{ + static void M(int i) { } + static void Main() + { + int x = 0; + F(x, (int y) => { }); + F(x, M); + } + static void F(int x, Action y) { } + static void F(int x, Action y) { } + static void F(int x, Func y, int z) { } +}"; + var comp = CreateCompilation(source); + var data = new InferredDelegateTypeData(); + comp.TestOnlyCompilationData = data; + comp.VerifyDiagnostics(); + Assert.Equal(0, data.InferredDelegateCount); + } + + /// + /// Similar to test above but with errors in the overloaded calls which means overload resolution + /// will consider more overloads when binding for error recovery. + /// + [Fact] + [WorkItem(1153265, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1153265")] + [WorkItem(58106, "https://github.com/dotnet/roslyn/issues/58106")] + public void InferDelegateType_02() + { + var source = +@"using System; +class Program +{ + static void M(int i) { } + static void Main() + { + F(x, (int y) => { }); + F(x, M); + } + static void F(int x, Action y) { } + static void F(int x, Action y) { } + static void F(int x, Func y, int z) { } +}"; + var comp = CreateCompilation(source); + var data = new InferredDelegateTypeData(); + comp.TestOnlyCompilationData = data; + comp.VerifyDiagnostics( + // (7,11): error CS0103: The name 'x' does not exist in the current context + // F(x, (int y) => { }); + Diagnostic(ErrorCode.ERR_NameNotInContext, "x").WithArguments("x").WithLocation(7, 11), + // (8,11): error CS0103: The name 'x' does not exist in the current context + // F(x, M); + Diagnostic(ErrorCode.ERR_NameNotInContext, "x").WithArguments("x").WithLocation(8, 11)); + Assert.Equal(0, data.InferredDelegateCount); + } + + [Fact] + public void InferDelegateType_03() + { + var source = +@"using System; +class Program +{ + static void M(int i) { } + static void Main() + { + int x = 0; + F(x, (int y) => { }); + F(x, M); + } + static void F(int x, Action y) { } + static void F(int x, Delegate y) { } +}"; + var comp = CreateCompilation(source); + var data = new InferredDelegateTypeData(); + comp.TestOnlyCompilationData = data; + comp.VerifyDiagnostics(); + Assert.Equal(2, data.InferredDelegateCount); + } + + [Fact] + public void InferDelegateType_04() + { + var source = +@"using System; +class Program +{ + static void M(int i) { } + static void Main() + { + F(x, (int y) => { }); + F(x, M); + } + static void F(int x, Action y) { } + static void F(int x, T y, int z) { } +}"; + var comp = CreateCompilation(source); + var data = new InferredDelegateTypeData(); + comp.TestOnlyCompilationData = data; + comp.VerifyDiagnostics( + // (7,11): error CS0103: The name 'x' does not exist in the current context + // F(x, (int y) => { }); + Diagnostic(ErrorCode.ERR_NameNotInContext, "x").WithArguments("x").WithLocation(7, 11), + // (8,11): error CS0103: The name 'x' does not exist in the current context + // F(x, M); + Diagnostic(ErrorCode.ERR_NameNotInContext, "x").WithArguments("x").WithLocation(8, 11)); + Assert.Equal(2, data.InferredDelegateCount); + } + + [Fact] + public void FunctionTypeSymbolOperations() + { + var source = +@"class Program +{ + static void Main() + { + } +}"; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var objectType = comp.GetSpecialType(SpecialType.System_Object); + var stringType = comp.GetSpecialType(SpecialType.System_String); + var funcOfT = comp.GetWellKnownType(WellKnownType.System_Func_T); + var funcOfObjectNullable = funcOfT.Construct(ImmutableArray.Create(TypeWithAnnotations.Create(objectType, NullableAnnotation.Annotated))); + var funcOfStringNullable = funcOfT.Construct(ImmutableArray.Create(TypeWithAnnotations.Create(stringType, NullableAnnotation.Annotated))); + var funcOfStringNotNullable = funcOfT.Construct(ImmutableArray.Create(TypeWithAnnotations.Create(stringType, NullableAnnotation.NotAnnotated))); + + var functionTypeObjectNullable = new FunctionTypeSymbol(funcOfObjectNullable); + var functionTypeStringNullable = new FunctionTypeSymbol(funcOfStringNullable); + var functionTypeStringNotNullable = new FunctionTypeSymbol(funcOfStringNotNullable); + var functionTypeNullA = new FunctionTypeSymbol(null!); + var functionTypeNullB = new FunctionTypeSymbol(null!); + + // MergeEquivalentTypes + Assert.Equal(functionTypeStringNullable, functionTypeStringNullable.MergeEquivalentTypes(functionTypeStringNullable, VarianceKind.Out)); + Assert.Equal(functionTypeStringNullable, functionTypeStringNullable.MergeEquivalentTypes(functionTypeStringNotNullable, VarianceKind.Out)); + Assert.Equal(functionTypeStringNullable, functionTypeStringNotNullable.MergeEquivalentTypes(functionTypeStringNullable, VarianceKind.Out)); + Assert.Equal(functionTypeStringNotNullable, functionTypeStringNotNullable.MergeEquivalentTypes(functionTypeStringNotNullable, VarianceKind.Out)); + Assert.Equal(functionTypeNullA, functionTypeNullA.MergeEquivalentTypes(functionTypeNullA, VarianceKind.Out)); + + // SetNullabilityForReferenceTypes + var setNotNullable = (TypeWithAnnotations type) => TypeWithAnnotations.Create(type.Type, NullableAnnotation.NotAnnotated); + Assert.Equal(functionTypeStringNotNullable, functionTypeStringNullable.SetNullabilityForReferenceTypes(setNotNullable)); + Assert.Equal(functionTypeNullA, functionTypeNullA.SetNullabilityForReferenceTypes(setNotNullable)); + + // Equals + Assert.True(functionTypeStringNotNullable.Equals(functionTypeStringNullable, TypeCompareKind.AllIgnoreOptions)); + Assert.False(functionTypeStringNotNullable.Equals(functionTypeStringNullable, TypeCompareKind.ConsiderEverything)); + Assert.False(functionTypeNullA.Equals(functionTypeStringNullable, TypeCompareKind.AllIgnoreOptions)); + Assert.False(functionTypeStringNullable.Equals(functionTypeNullA, TypeCompareKind.AllIgnoreOptions)); + Assert.True(functionTypeNullA.Equals(functionTypeNullA, TypeCompareKind.ConsiderEverything)); + Assert.False(functionTypeNullA.Equals(functionTypeNullB, TypeCompareKind.ConsiderEverything)); + + // GetHashCode + Assert.Equal(functionTypeStringNullable.GetHashCode(), functionTypeStringNotNullable.GetHashCode()); + Assert.Equal(functionTypeNullA.GetHashCode(), functionTypeNullB.GetHashCode()); + + // ConversionsBase.ClassifyImplicitConversionFromTypeWhenNeitherOrBothFunctionTypes + var conversions = new TypeConversions(comp.SourceAssembly.CorLibrary); + var useSiteInfo = CompoundUseSiteInfo.Discarded; + Assert.Equal(ConversionKind.FunctionType, conversions.ClassifyImplicitConversionFromTypeWhenNeitherOrBothFunctionTypes(functionTypeStringNullable, functionTypeStringNotNullable, ref useSiteInfo).Kind); + Assert.Equal(ConversionKind.FunctionType, conversions.ClassifyImplicitConversionFromTypeWhenNeitherOrBothFunctionTypes(functionTypeStringNullable, functionTypeObjectNullable, ref useSiteInfo).Kind); + Assert.Equal(ConversionKind.NoConversion, conversions.ClassifyImplicitConversionFromTypeWhenNeitherOrBothFunctionTypes(functionTypeStringNullable, functionTypeNullA, ref useSiteInfo).Kind); + Assert.Equal(ConversionKind.NoConversion, conversions.ClassifyImplicitConversionFromTypeWhenNeitherOrBothFunctionTypes(functionTypeNullA, functionTypeStringNullable, ref useSiteInfo).Kind); + Assert.Equal(ConversionKind.NoConversion, conversions.ClassifyImplicitConversionFromTypeWhenNeitherOrBothFunctionTypes(functionTypeNullA, functionTypeNullA, ref useSiteInfo).Kind); + } + [Fact] public void TaskRunArgument() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableContextTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableContextTests.cs index 5c80ffac51c0b..ed0c679693c77 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableContextTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableContextTests.cs @@ -194,7 +194,7 @@ class Program void verify(CSharpParseOptions parseOptions, params string[] expectedAnalyzedKeys) { var comp = CreateCompilation(source, parseOptions: parseOptions); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); if (expectedAnalyzedKeys.Length > 0) { comp.VerifyDiagnostics( @@ -207,7 +207,7 @@ void verify(CSharpParseOptions parseOptions, params string[] expectedAnalyzedKey comp.VerifyDiagnostics(); } - var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.NullableAnalysisData); + var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData); AssertEx.Equal(expectedAnalyzedKeys, actualAnalyzedKeys); } } @@ -243,7 +243,7 @@ static void F2(object obj = C2) { } void verify(CSharpParseOptions parseOptions, params string[] expectedAnalyzedKeys) { var comp = CreateCompilation(source, parseOptions: parseOptions); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); if (expectedAnalyzedKeys.Length > 0) { comp.VerifyDiagnostics( @@ -256,7 +256,7 @@ void verify(CSharpParseOptions parseOptions, params string[] expectedAnalyzedKey comp.VerifyDiagnostics(); } - var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.NullableAnalysisData); + var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData); AssertEx.Equal(expectedAnalyzedKeys, actualAnalyzedKeys); } } @@ -302,7 +302,7 @@ struct B2 void verify(CSharpParseOptions parseOptions, params string[] expectedAnalyzedKeys) { var comp = CreateCompilation(sourceB, references: new[] { refA }, parseOptions: parseOptions); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); if (expectedAnalyzedKeys.Length > 0) { comp.VerifyDiagnostics( @@ -315,7 +315,7 @@ void verify(CSharpParseOptions parseOptions, params string[] expectedAnalyzedKey comp.VerifyDiagnostics(); } - var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.NullableAnalysisData); + var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData); AssertEx.Equal(expectedAnalyzedKeys, actualAnalyzedKeys); } } @@ -344,7 +344,7 @@ static object F(object? obj) void verify(CSharpParseOptions parseOptions, bool expectedFlowState, params string[] expectedAnalyzedKeys) { var comp = CreateCompilation(source, parseOptions: parseOptions); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); var syntaxTree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(syntaxTree); var syntax = syntaxTree.GetRoot().DescendantNodes().OfType().Skip(1).Single(); @@ -353,7 +353,7 @@ void verify(CSharpParseOptions parseOptions, bool expectedFlowState, params stri var expectedNullability = expectedFlowState ? Microsoft.CodeAnalysis.NullableFlowState.NotNull : Microsoft.CodeAnalysis.NullableFlowState.None; Assert.Equal(expectedNullability, typeInfo.Nullability.FlowState); - var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.NullableAnalysisData); + var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData); AssertEx.Equal(expectedAnalyzedKeys, actualAnalyzedKeys); } } @@ -383,7 +383,7 @@ class B void verify(CSharpParseOptions parseOptions, bool expectedFlowState, params string[] expectedAnalyzedKeys) { var comp = CreateCompilation(source, parseOptions: parseOptions); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); var syntaxTree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(syntaxTree); var syntax = syntaxTree.GetRoot().DescendantNodes().OfType().Single(); @@ -392,7 +392,7 @@ void verify(CSharpParseOptions parseOptions, bool expectedFlowState, params stri var expectedNullability = expectedFlowState ? Microsoft.CodeAnalysis.NullableFlowState.MaybeNull : Microsoft.CodeAnalysis.NullableFlowState.None; Assert.Equal(expectedNullability, typeInfo.Nullability.FlowState); - var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.NullableAnalysisData); + var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData); AssertEx.Equal(expectedAnalyzedKeys, actualAnalyzedKeys); } } @@ -418,7 +418,7 @@ static void M(object arg = (F = null)) { } void verify(CSharpParseOptions parseOptions, bool expectedFlowState, params string[] expectedAnalyzedKeys) { var comp = CreateCompilation(source, parseOptions: parseOptions); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); var syntaxTree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(syntaxTree); var syntax = syntaxTree.GetRoot().DescendantNodes().OfType().First().Value; @@ -427,7 +427,7 @@ void verify(CSharpParseOptions parseOptions, bool expectedFlowState, params stri var expectedNullability = expectedFlowState ? Microsoft.CodeAnalysis.NullableFlowState.MaybeNull : Microsoft.CodeAnalysis.NullableFlowState.None; Assert.Equal(expectedNullability, typeInfo.Nullability.FlowState); - var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.NullableAnalysisData); + var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData); AssertEx.Equal(expectedAnalyzedKeys, actualAnalyzedKeys); } } @@ -558,7 +558,7 @@ static class B var options = TestOptions.ReleaseDll; if (projectContext != null) options = options.WithNullableContextOptions(projectContext.Value); var comp = CreateCompilation(sourceB, options: options, references: new[] { refA }); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); if (isNullableEnabledForMethod) { @@ -572,7 +572,7 @@ static class B comp.VerifyDiagnostics(); } - var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true); + var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true); Assert.Equal(isNullableEnabledForMethod, actualAnalyzedKeys.Contains("Main")); var tree = (CSharpSyntaxTree)comp.SyntaxTrees[0]; @@ -721,11 +721,11 @@ static object M8() => static void verify(string source, string[] expectedAnalyzedKeys, params DiagnosticDescription[] expectedDiagnostics) { var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); comp.VerifyDiagnostics(expectedDiagnostics); - AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true)); - AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.NullableAnalysisData)); + AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true)); + AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.TestOnlyCompilationData)); var tree = (CSharpSyntaxTree)comp.SyntaxTrees[0]; var methodDeclarations = tree.GetRoot().DescendantNodes().OfType().ToArray(); @@ -981,11 +981,11 @@ struct S static void verify(string source, string[] expectedAnalyzedKeys, params DiagnosticDescription[] expectedDiagnostics) { var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); comp.VerifyDiagnostics(expectedDiagnostics); - AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true)); - AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.NullableAnalysisData)); + AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true)); + AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.TestOnlyCompilationData)); } } @@ -1062,11 +1062,11 @@ partial class Program static void verify(string[] source, CSharpCompilationOptions options, string[] expectedAnalyzedKeys, params DiagnosticDescription[] expectedDiagnostics) { var comp = CreateCompilation(source, options: options); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); comp.VerifyDiagnostics(expectedDiagnostics); - AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true)); - AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.NullableAnalysisData)); + AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true)); + AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.TestOnlyCompilationData)); } } @@ -1146,11 +1146,11 @@ class Program static void verify(string source, string[] expectedAnalyzedKeys, params DiagnosticDescription[] expectedDiagnostics) { var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); comp.VerifyDiagnostics(expectedDiagnostics); - AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true)); - AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.NullableAnalysisData)); + AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true)); + AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.TestOnlyCompilationData)); } } @@ -1205,11 +1205,11 @@ class Program static void verify(string source, string[] expectedAnalyzedKeys, params DiagnosticDescription[] expectedDiagnostics) { var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); comp.VerifyDiagnostics(expectedDiagnostics); - AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true)); - AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.NullableAnalysisData)); + AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true)); + AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.TestOnlyCompilationData)); } } @@ -1296,11 +1296,11 @@ event D F static void verify(string source, string[] expectedAnalyzedKeys, params DiagnosticDescription[] expectedDiagnostics) { var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); comp.VerifyDiagnostics(expectedDiagnostics); - AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true)); - AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.NullableAnalysisData)); + AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true)); + AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.TestOnlyCompilationData)); } } @@ -1353,11 +1353,11 @@ class B static void verify(string source, string[] expectedAnalyzedKeys, params DiagnosticDescription[] expectedDiagnostics) { var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); comp.VerifyDiagnostics(expectedDiagnostics); - AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true)); - AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.NullableAnalysisData)); + AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true)); + AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.TestOnlyCompilationData)); } } @@ -1448,10 +1448,10 @@ record B2() : A( static void verify(string source, string[] expectedAnalyzedKeys, params DiagnosticDescription[] expectedDiagnostics) { var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); comp.VerifyDiagnostics(expectedDiagnostics); - var actualAnalyzedKeys = GetIsNullableEnabledMethods(comp.NullableAnalysisData, key => ((MethodSymbol)key).ToTestDisplayString()); + var actualAnalyzedKeys = GetIsNullableEnabledMethods(comp.TestOnlyCompilationData, key => ((MethodSymbol)key).ToTestDisplayString()); AssertEx.Equal(expectedAnalyzedKeys, actualAnalyzedKeys); } } @@ -1482,7 +1482,7 @@ public void AnalyzeMethodsInEnabledContextOnly_10() }"; var comp = CreateCompilation(new[] { source1, source2 }); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); comp.VerifyDiagnostics( // (4,43): warning CS8625: Cannot convert null literal to non-nullable reference type. // partial void F1(ref object o1) { o1 = null; } @@ -1492,8 +1492,8 @@ public void AnalyzeMethodsInEnabledContextOnly_10() Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(8, 43)); var expectedAnalyzedKeys = new[] { "F1", "F4" }; - AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true)); - AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.NullableAnalysisData)); + AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true)); + AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.TestOnlyCompilationData)); } [Fact] @@ -1512,7 +1512,7 @@ partial void F1(object x = null) { } }"; var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); comp.VerifyDiagnostics( // (4,32): warning CS8625: Cannot convert null literal to non-nullable reference type. // partial void F1(object x = null); @@ -1527,7 +1527,7 @@ partial void F1(object x = null) { } // partial void F1(object x = null) { } Diagnostic(ErrorCode.WRN_DefaultValueForUnconsumedLocation, "x").WithArguments("x").WithLocation(7, 28)); - var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true); + var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true); AssertEx.Equal(new[] { "= null", "= null", "F2" }, actualAnalyzedKeys); } @@ -1583,10 +1583,10 @@ class A : System.Attribute }"; var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); comp.VerifyDiagnostics(); - var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true); + var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true); var expectedAnalyzedKeys = new[] { ".cctor", @@ -1735,11 +1735,11 @@ static void verify(string[] source, NullableContextOptions? projectContext, stri var options = TestOptions.ReleaseExe; if (projectContext != null) options = options.WithNullableContextOptions(projectContext.GetValueOrDefault()); var comp = CreateCompilation(source, options: options); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); comp.VerifyDiagnostics(expectedDiagnostics); - AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true)); - AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.NullableAnalysisData)); + AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true)); + AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.TestOnlyCompilationData)); } } @@ -1765,7 +1765,7 @@ static object F2(object o2) }"; var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); var syntaxTree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(syntaxTree); var returnStatements = syntaxTree.GetRoot().DescendantNodes().OfType().ToArray(); @@ -1781,8 +1781,8 @@ static object F2(object o2) Assert.Equal(Microsoft.CodeAnalysis.NullableFlowState.None, typeInfo.Nullability.FlowState); var expectedAnalyzedKeys = new[] { "F1" }; - AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true)); - AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.NullableAnalysisData)); + AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true)); + AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.TestOnlyCompilationData)); } [Fact] @@ -1834,7 +1834,7 @@ public void AnalyzeMethodsInEnabledContextOnly_MethodBodySemanticModel_02() static void verify(string source, Microsoft.CodeAnalysis.NullableFlowState expectedFlowState, params string[] expectedAnalyzedKeys) { var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); var syntaxTree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(syntaxTree); @@ -1843,8 +1843,8 @@ static void verify(string source, Microsoft.CodeAnalysis.NullableFlowState expec var typeInfo = model.GetTypeInfo(syntax); Assert.Equal(expectedFlowState, typeInfo.Nullability.FlowState); - AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true)); - AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.NullableAnalysisData)); + AssertEx.Equal(expectedAnalyzedKeys, GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true)); + AssertEx.Equal(expectedAnalyzedKeys, GetIsNullableEnabledMethods(comp.TestOnlyCompilationData)); } } @@ -1872,7 +1872,7 @@ class B2 }"; var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); var syntaxTree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(syntaxTree); var attributeArguments = syntaxTree.GetRoot().DescendantNodes().OfType().ToArray(); @@ -1880,7 +1880,7 @@ class B2 verify(attributeArguments[0], "A.F1 = null", Microsoft.CodeAnalysis.NullableFlowState.MaybeNull); verify(attributeArguments[1], "A.F2 = null", Microsoft.CodeAnalysis.NullableFlowState.None); - var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true); + var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true); AssertEx.Equal(new[] { "A(A.F1 = null)" }, actualAnalyzedKeys); void verify(AttributeArgumentSyntax syntax, string expectedText, Microsoft.CodeAnalysis.NullableFlowState expectedFlowState) @@ -1921,7 +1921,7 @@ object x }"; var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); var syntaxTree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(syntaxTree); var attributeArguments = syntaxTree.GetRoot().DescendantNodes().OfType().ToArray(); @@ -1929,7 +1929,7 @@ object x verify(attributeArguments[0], "C1", Microsoft.CodeAnalysis.NullableFlowState.MaybeNull); verify(attributeArguments[1], "C2", Microsoft.CodeAnalysis.NullableFlowState.None); - var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true); + var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true); var expectedAnalyzedKey = @"DefaultParameterValue( #nullable enable @@ -1963,7 +1963,7 @@ static void M2(object o2 = (F2 = null)) { } }"; var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); var syntaxTree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(syntaxTree); var equalsValueClauses = syntaxTree.GetRoot().DescendantNodes().OfType().ToArray(); @@ -1971,7 +1971,7 @@ static void M2(object o2 = (F2 = null)) { } verify(equalsValueClauses[0], "(F1 = null)", Microsoft.CodeAnalysis.NullableFlowState.MaybeNull); verify(equalsValueClauses[1], "(F2 = null)", Microsoft.CodeAnalysis.NullableFlowState.None); - var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true); + var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true); AssertEx.Equal(new[] { "o1" }, actualAnalyzedKeys); void verify(EqualsValueClauseSyntax syntax, string expectedText, Microsoft.CodeAnalysis.NullableFlowState expectedFlowState) @@ -2003,7 +2003,7 @@ class B }"; var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); var syntaxTree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(syntaxTree); var declarations = syntaxTree.GetRoot().DescendantNodes().OfType().Select(f => f.Declaration.Variables[0]).ToArray(); @@ -2012,7 +2012,7 @@ class B verify(declarations[1], "F2", Microsoft.CodeAnalysis.NullableFlowState.MaybeNull); verify(declarations[2], "F3", Microsoft.CodeAnalysis.NullableFlowState.MaybeNull); - var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true); + var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true); AssertEx.Equal(new[] { "F2", "F3" }, actualAnalyzedKeys); void verify(VariableDeclaratorSyntax syntax, string expectedText, Microsoft.CodeAnalysis.NullableFlowState expectedFlowState) @@ -2042,7 +2042,7 @@ class B }"; var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(); var syntaxTree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(syntaxTree); var declarations = syntaxTree.GetRoot().DescendantNodes().OfType().ToArray(); @@ -2051,7 +2051,7 @@ class B verify(declarations[1], "P2", Microsoft.CodeAnalysis.NullableFlowState.MaybeNull); verify(declarations[2], "P3", Microsoft.CodeAnalysis.NullableFlowState.MaybeNull); - var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.NullableAnalysisData, requiredAnalysis: true); + var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.TestOnlyCompilationData, requiredAnalysis: true); AssertEx.Equal(new[] { "P2", "P3" }, actualAnalyzedKeys); void verify(PropertyDeclarationSyntax syntax, string expectedText, Microsoft.CodeAnalysis.NullableFlowState expectedFlowState) @@ -2232,17 +2232,19 @@ private static void VerifySpeculativeSemanticModel(string source, NullableContex Assert.Equal(expectedAnnotation, typeInfo.Nullability.Annotation); } - private static string[] GetNullableDataKeysAsStrings(CSharpCompilation.NullableData nullableData, bool requiredAnalysis = false) => - nullableData.Data. + private static string[] GetNullableDataKeysAsStrings(object compilationData, bool requiredAnalysis = false) + { + return ((NullableWalker.NullableAnalysisData)compilationData).Data. Where(pair => !requiredAnalysis || pair.Value.RequiredAnalysis). Select(pair => GetNullableDataKeyAsString(pair.Key)). OrderBy(key => key). ToArray(); + } - private static string[] GetIsNullableEnabledMethods(CSharpCompilation.NullableData nullableData, Func toString = null) + private static string[] GetIsNullableEnabledMethods(object compilationData, Func toString = null) { toString ??= GetNullableDataKeyAsString; - return nullableData.Data. + return ((NullableWalker.NullableAnalysisData)compilationData).Data. Where(pair => pair.Value.RequiredAnalysis && pair.Key is MethodSymbol method && method.IsNullableAnalysisEnabled()). Select(pair => toString(pair.Key)). OrderBy(key => key). diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionPerfTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionPerfTests.cs index 10e4e7f56ef71..32ef2edbcaf87 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionPerfTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionPerfTests.cs @@ -4,6 +4,7 @@ #nullable disable +using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Roslyn.Test.Utilities; using System.Linq; @@ -394,10 +395,11 @@ public void AnalyzeMethodsInEnabledContextOnly() var source = builder.ToString(); var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + var nullableAnalysisData = new NullableWalker.NullableAnalysisData(); + comp.TestOnlyCompilationData = nullableAnalysisData; comp.VerifyDiagnostics(); - int analyzed = comp.NullableAnalysisData.Data.Where(pair => pair.Value.RequiredAnalysis).Count(); + int analyzed = nullableAnalysisData.Data.Where(pair => pair.Value.RequiredAnalysis).Count(); Assert.Equal(nMethods / 2, analyzed); } @@ -423,11 +425,12 @@ public void NullableStateLambdas() var source = builder.ToString(); var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + var nullableAnalysisData = new NullableWalker.NullableAnalysisData(); + comp.TestOnlyCompilationData = nullableAnalysisData; comp.VerifyDiagnostics(); var method = comp.GetMember("Program.F2"); - Assert.Equal(1, comp.NullableAnalysisData.Data[method].TrackedEntries); + Assert.Equal(1, nullableAnalysisData.Data[method].TrackedEntries); } [Fact] @@ -452,11 +455,12 @@ public void NullableStateLocalFunctions() var source = builder.ToString(); var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(); + var nullableAnalysisData = new NullableWalker.NullableAnalysisData(); + comp.TestOnlyCompilationData = nullableAnalysisData; comp.VerifyDiagnostics(); var method = comp.GetMember("Program.F"); - Assert.Equal(1, comp.NullableAnalysisData.Data[method].TrackedEntries); + Assert.Equal(1, nullableAnalysisData.Data[method].TrackedEntries); } [ConditionalFact(typeof(NoIOperationValidation))] @@ -575,7 +579,7 @@ public void NullableAnalysisNestedExpressionsInMethod() var source = builder.ToString(); var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(maxRecursionDepth: nestingLevel / 2); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(maxRecursionDepth: nestingLevel / 2); comp.VerifyDiagnostics( // (7,15): error CS8078: An expression is too long or complex to compile // C c = new C() @@ -611,7 +615,7 @@ public void NullableAnalysisNestedExpressionsInLocalFunction() var source = builder.ToString(); var comp = CreateCompilation(source); - comp.NullableAnalysisData = new(maxRecursionDepth: nestingLevel / 2); + comp.TestOnlyCompilationData = new NullableWalker.NullableAnalysisData(maxRecursionDepth: nestingLevel / 2); comp.VerifyDiagnostics( // (10,15): error CS8078: An expression is too long or complex to compile // C c = new C() diff --git a/src/Compilers/Core/CodeAnalysisTest/InternalUtilities/AdditionalTextComparerTests.cs b/src/Compilers/Core/CodeAnalysisTest/InternalUtilities/AdditionalTextComparerTests.cs new file mode 100644 index 0000000000000..62ab6fab28512 --- /dev/null +++ b/src/Compilers/Core/CodeAnalysisTest/InternalUtilities/AdditionalTextComparerTests.cs @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Roslyn.Test.Utilities.TestGenerators; +using Roslyn.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.UnitTests.InternalUtilities +{ + public class AdditionalTextComparerTests + { + [Fact] + public void Compares_Equal_When_Path_And_Content_Are_The_Same() + { + AdditionalText text1 = new InMemoryAdditionalText(@"c:\a\b\c.txt", "abc"); + AdditionalText text2 = new InMemoryAdditionalText(@"c:\a\b\c.txt", "abc"); + + Assert.NotEqual(text1, text2); + Assert.Equal(text1, text2, AdditionalTextComparer.Instance); + } + + [Fact] + public void HashCodes_Match_When_Path_And_Content_Are_The_Same() + { + AdditionalText text1 = new InMemoryAdditionalText(@"c:\a\b\c.txt", "abc"); + AdditionalText text2 = new InMemoryAdditionalText(@"c:\a\b\c.txt", "abc"); + + var hash1 = text1.GetHashCode(); + var hash2 = text2.GetHashCode(); + Assert.NotEqual(hash1, hash2); + + var comparerHash1 = AdditionalTextComparer.Instance.GetHashCode(text1); + var comparerHash2 = AdditionalTextComparer.Instance.GetHashCode(text2); + Assert.Equal(comparerHash1, comparerHash2); + } + + [Fact] + public void Not_Equal_When_Paths_Differ() + { + AdditionalText text1 = new InMemoryAdditionalText(@"c:\a\b\c.txt", "abc"); + AdditionalText text2 = new InMemoryAdditionalText(@"c:\d\e\f.txt", "abc"); + + Assert.NotEqual(text1, text2, AdditionalTextComparer.Instance); + + var comparerHash1 = AdditionalTextComparer.Instance.GetHashCode(text1); + var comparerHash2 = AdditionalTextComparer.Instance.GetHashCode(text2); + Assert.NotEqual(comparerHash1, comparerHash2); + } + + [Fact] + public void Not_Equal_When_Contents_Differ() + { + AdditionalText text1 = new InMemoryAdditionalText(@"c:\a\b\c.txt", "abc"); + AdditionalText text2 = new InMemoryAdditionalText(@"c:\a\b\c.txt", "def"); + + Assert.NotEqual(text1, text2, AdditionalTextComparer.Instance); + + var comparerHash1 = AdditionalTextComparer.Instance.GetHashCode(text1); + var comparerHash2 = AdditionalTextComparer.Instance.GetHashCode(text2); + Assert.NotEqual(comparerHash1, comparerHash2); + } + + [Fact] + public void Comparison_With_Different_Path_Casing() + { + AdditionalText text1 = new InMemoryAdditionalText(@"c:\a\b\c.txt", "abc"); + AdditionalText text2 = new InMemoryAdditionalText(@"c:\a\B\c.txt", "abc"); + + // hash codes are path case insensitive + var comparerHash1 = AdditionalTextComparer.Instance.GetHashCode(text1); + var comparerHash2 = AdditionalTextComparer.Instance.GetHashCode(text2); + Assert.Equal(comparerHash1, comparerHash2); + + // but we correctly compare + if (PathUtilities.IsUnixLikePlatform) + { + Assert.NotEqual(text1, text2, AdditionalTextComparer.Instance); + } + else + { + Assert.Equal(text1, text2, AdditionalTextComparer.Instance); + } + } + } +} diff --git a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs index 5497ab5a8ec92..9e61cff4ef2f5 100644 --- a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs +++ b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs @@ -744,7 +744,10 @@ private protected Compilation RunGenerators(Compilation input, ParseOptions pars if (this.GeneratorDriverCache is object && !disableCache) { cacheKey = deriveCacheKey(); - driver = this.GeneratorDriverCache.TryGetDriver(cacheKey); + driver = this.GeneratorDriverCache.TryGetDriver(cacheKey)? + .WithUpdatedParseOptions(parseOptions) + .WithUpdatedAnalyzerConfigOptions(analyzerConfigOptionsProvider) + .ReplaceAdditionalTexts(additionalTexts); } driver ??= CreateGeneratorDriver(parseOptions, generators, analyzerConfigOptionsProvider, additionalTexts); @@ -1445,14 +1448,19 @@ private bool WriteTouchedFiles(DiagnosticBag diagnostics, TouchedFileLogger? tou protected virtual ImmutableArray ResolveAdditionalFilesFromArguments(List diagnostics, CommonMessageProvider messageProvider, TouchedFileLogger? touchedFilesLogger) { - var builder = ImmutableArray.CreateBuilder(); + var builder = ArrayBuilder.GetInstance(); + var filePaths = new HashSet(PathUtilities.Comparer); foreach (var file in Arguments.AdditionalFiles) { - builder.Add(new AdditionalTextFile(file, this)); + Debug.Assert(PathUtilities.IsAbsolute(file.Path)); + if (filePaths.Add(PathUtilities.ExpandAbsolutePathWithRelativeParts(file.Path))) + { + builder.Add(new AdditionalTextFile(file, this)); + } } - return builder.ToImmutableArray(); + return builder.ToImmutableAndFree(); } private static void ReportAnalyzerExecutionTime(TextWriter consoleOutput, AnalyzerDriver analyzerDriver, CultureInfo culture, bool isConcurrentBuild) diff --git a/src/Compilers/Core/Portable/InternalUtilities/AdditionalTextComparer.cs b/src/Compilers/Core/Portable/InternalUtilities/AdditionalTextComparer.cs new file mode 100644 index 0000000000000..fe7ed89f21174 --- /dev/null +++ b/src/Compilers/Core/Portable/InternalUtilities/AdditionalTextComparer.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.Collections; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis +{ + internal sealed class AdditionalTextComparer : IEqualityComparer + { + public static readonly AdditionalTextComparer Instance = new AdditionalTextComparer(); + + public bool Equals(AdditionalText? x, AdditionalText? y) + { + if (object.ReferenceEquals(x, y)) + { + return true; + } + + if (x is null || y is null) + { + return false; + } + + if (!PathUtilities.Comparer.Equals(x.Path, y.Path)) + { + return false; + } + + var xText = x.GetText(); + var yText = y.GetText(); + + if (xText is null || yText is null || xText.Length != yText.Length) + { + return false; + } + + return ByteSequenceComparer.Equals(xText.GetChecksum(), yText.GetChecksum()); + } + + public int GetHashCode(AdditionalText obj) + { + return Hash.Combine(PathUtilities.Comparer.GetHashCode(obj.Path), + ByteSequenceComparer.GetHashCode(obj.GetText()?.GetChecksum() ?? ImmutableArray.Empty)); + } + } +} diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 275b1da6d6977..4c59f0ad9066c 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -16,6 +16,7 @@ Microsoft.CodeAnalysis.FlowAnalysis.IFlowCaptureReferenceOperation.IsInitializat Microsoft.CodeAnalysis.GeneratorAttribute.GeneratorAttribute(string! firstLanguage, params string![]! additionalLanguages) -> void Microsoft.CodeAnalysis.GeneratorAttribute.Languages.get -> string![]! Microsoft.CodeAnalysis.GeneratorDriver.ReplaceAdditionalText(Microsoft.CodeAnalysis.AdditionalText! oldText, Microsoft.CodeAnalysis.AdditionalText! newText) -> Microsoft.CodeAnalysis.GeneratorDriver! +Microsoft.CodeAnalysis.GeneratorDriver.ReplaceAdditionalTexts(System.Collections.Immutable.ImmutableArray newTexts) -> Microsoft.CodeAnalysis.GeneratorDriver! Microsoft.CodeAnalysis.GeneratorDriver.WithUpdatedAnalyzerConfigOptions(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider! newOptions) -> Microsoft.CodeAnalysis.GeneratorDriver! Microsoft.CodeAnalysis.GeneratorDriver.WithUpdatedParseOptions(Microsoft.CodeAnalysis.ParseOptions! newOptions) -> Microsoft.CodeAnalysis.GeneratorDriver! Microsoft.CodeAnalysis.GeneratorDriverOptions diff --git a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs index 15a8e74bb3f56..4cdd7d502ad4b 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs @@ -120,6 +120,8 @@ public GeneratorDriver ReplaceAdditionalText(AdditionalText oldText, AdditionalT return FromState(newState); } + public GeneratorDriver ReplaceAdditionalTexts(ImmutableArray newTexts) => FromState(_state.With(additionalTexts: newTexts)); + public GeneratorDriver WithUpdatedParseOptions(ParseOptions newOptions) => newOptions is object ? FromState(_state.With(parseOptions: newOptions)) : throw new ArgumentNullException(nameof(newOptions)); diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/InputNode.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/InputNode.cs index 78aefa4e656b7..96af759a55bb0 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/InputNode.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/InputNode.cs @@ -20,18 +20,20 @@ internal sealed class InputNode : IIncrementalGeneratorNode { private readonly Func> _getInput; private readonly Action _registerOutput; + private readonly IEqualityComparer _inputComparer; private readonly IEqualityComparer _comparer; private readonly string? _name; - public InputNode(Func> getInput) - : this(getInput, registerOutput: null, comparer: null) + public InputNode(Func> getInput, IEqualityComparer? inputComparer = null) + : this(getInput, registerOutput: null, inputComparer: inputComparer, comparer: null) { } - private InputNode(Func> getInput, Action? registerOutput, IEqualityComparer? comparer = null, string? name = null) + private InputNode(Func> getInput, Action? registerOutput, IEqualityComparer? inputComparer = null, IEqualityComparer? comparer = null, string? name = null) { _getInput = getInput; _comparer = comparer ?? EqualityComparer.Default; + _inputComparer = inputComparer ?? EqualityComparer.Default; _registerOutput = registerOutput ?? (o => throw ExceptionUtilities.Unreachable); _name = name; } @@ -43,7 +45,7 @@ public NodeStateTable UpdateStateTable(DriverStateTable.Builder graphState, N TimeSpan elapsedTime = stopwatch.Elapsed; // create a mutable hashset of the new items we can check against - HashSet itemsSet = new HashSet(); + HashSet itemsSet = new HashSet(_inputComparer); foreach (var item in inputItems) { var added = itemsSet.Add(item); @@ -92,10 +94,11 @@ public NodeStateTable UpdateStateTable(DriverStateTable.Builder graphState, N return builder.ToImmutableAndFree(); } - public IIncrementalGeneratorNode WithComparer(IEqualityComparer comparer) => new InputNode(_getInput, _registerOutput, comparer, _name); - public IIncrementalGeneratorNode WithTrackingName(string name) => new InputNode(_getInput, _registerOutput, _comparer, name); + public IIncrementalGeneratorNode WithComparer(IEqualityComparer comparer) => new InputNode(_getInput, _registerOutput, _inputComparer, comparer, _name); - public InputNode WithRegisterOutput(Action registerOutput) => new InputNode(_getInput, registerOutput, _comparer); + public IIncrementalGeneratorNode WithTrackingName(string name) => new InputNode(_getInput, _registerOutput, _inputComparer, _comparer, name); + + public InputNode WithRegisterOutput(Action registerOutput) => new InputNode(_getInput, registerOutput, _inputComparer, _comparer, _name); public void RegisterOutput(IIncrementalGeneratorOutputNode output) => _registerOutput(output); } diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SharedInputNodes.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/SharedInputNodes.cs index 5e6bd89ec6b05..558094a36505e 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SharedInputNodes.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/SharedInputNodes.cs @@ -3,10 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis { @@ -19,7 +16,7 @@ internal static class SharedInputNodes public static readonly InputNode ParseOptions = new InputNode(b => ImmutableArray.Create(b.DriverState.ParseOptions)); - public static readonly InputNode AdditionalTexts = new InputNode(b => b.DriverState.AdditionalTexts); + public static readonly InputNode AdditionalTexts = new InputNode(b => b.DriverState.AdditionalTexts, AdditionalTextComparer.Instance); public static readonly InputNode SyntaxTrees = new InputNode(b => b.Compilation.SyntaxTrees.ToImmutableArray()); diff --git a/src/Compilers/Test/Core/Assert/ConditionalFactAttribute.cs b/src/Compilers/Test/Core/Assert/ConditionalFactAttribute.cs index cc168c964494a..241280df192a8 100644 --- a/src/Compilers/Test/Core/Assert/ConditionalFactAttribute.cs +++ b/src/Compilers/Test/Core/Assert/ConditionalFactAttribute.cs @@ -312,6 +312,12 @@ public class WindowsOrLinuxOnly : ExecutionCondition public override string SkipReason => "Test not supported on macOS"; } + public class LinuxOnly : ExecutionCondition + { + public override bool ShouldSkip => !ExecutionConditionUtil.IsLinux; + public override string SkipReason => "Test not supported on Windows or macOS"; + } + public class ClrOnly : ExecutionCondition { public override bool ShouldSkip => MonoHelpers.IsRunningOnMono(); diff --git a/src/EditorFeatures/CSharpTest/ImplementAbstractClass/ImplementAbstractClassTests.cs b/src/EditorFeatures/CSharpTest/ImplementAbstractClass/ImplementAbstractClassTests.cs index 6fa2a987c3ce0..5bcd6dca784d2 100644 --- a/src/EditorFeatures/CSharpTest/ImplementAbstractClass/ImplementAbstractClassTests.cs +++ b/src/EditorFeatures/CSharpTest/ImplementAbstractClass/ImplementAbstractClassTests.cs @@ -1488,7 +1488,7 @@ class Derived : Base void Goo() { } public override int Prop => throw new System.NotImplementedException(); -}", options: Option(ImplementTypeOptions.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd)); +}", options: Option(ImplementTypeOptions.Metadata.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd)); } [WorkItem(17274, "https://github.com/dotnet/roslyn/issues/17274")] @@ -1665,7 +1665,7 @@ class C : AbstractClass public override int ReadWriteProp { get; set; } public override int WriteOnlyProp { set => throw new System.NotImplementedException(); } }", parameters: new TestParameters(options: Option( - ImplementTypeOptions.PropertyGenerationBehavior, + ImplementTypeOptions.Metadata.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties))); } diff --git a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs index 107b93ca985e5..66ffe34f3a66f 100644 --- a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs +++ b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs @@ -7287,7 +7287,7 @@ void M() { } }", Options = { - { ImplementTypeOptions.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd }, + { ImplementTypeOptions.Metadata.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd }, }, }.RunAsync(); } @@ -7462,7 +7462,7 @@ class Class : IInterface }", Options = { - { ImplementTypeOptions.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties }, + { ImplementTypeOptions.Metadata.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties }, }, }.RunAsync(); } diff --git a/src/EditorFeatures/CSharpTest/Structure/MetadataAsSource/InvalidIdentifierStructureTests.cs b/src/EditorFeatures/CSharpTest/Structure/MetadataAsSource/InvalidIdentifierStructureTests.cs index 6b5a188ebef8e..531afa24ce496 100644 --- a/src/EditorFeatures/CSharpTest/Structure/MetadataAsSource/InvalidIdentifierStructureTests.cs +++ b/src/EditorFeatures/CSharpTest/Structure/MetadataAsSource/InvalidIdentifierStructureTests.cs @@ -29,8 +29,8 @@ public class InvalidIdentifierStructureTests : AbstractSyntaxStructureProviderTe internal override async Task> GetBlockSpansWorkerAsync(Document document, int position) { var outliningService = document.GetLanguageService(); - - return (await outliningService.GetBlockStructureAsync(document, CancellationToken.None)).Spans; + var options = BlockStructureOptions.From(document.Project); + return (await outliningService.GetBlockStructureAsync(document, options, CancellationToken.None)).Spans; } [WorkItem(1174405, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1174405")] diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/AlwaysActivateInProcLanguageClient.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/AlwaysActivateInProcLanguageClient.cs index 629bf9c50e39d..657502f8e140d 100644 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/AlwaysActivateInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/Implementation/LanguageServer/AlwaysActivateInProcLanguageClient.cs @@ -72,7 +72,14 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa }; } - serverCapabilities.SupportsDiagnosticRequests = GlobalOptions.IsPullDiagnostics(InternalDiagnosticsOptions.NormalDiagnosticMode); + serverCapabilities.ProjectContextProvider = true; + + var isPullDiagnostics = GlobalOptions.IsPullDiagnostics(InternalDiagnosticsOptions.NormalDiagnosticMode); + if (isPullDiagnostics) + { + serverCapabilities.SupportsDiagnosticRequests = true; + serverCapabilities.MultipleContextSupportProvider = new VSInternalMultipleContextFeatures { SupportsMultipleContextsDiagnostics = true }; + } // This capability is always enabled as we provide cntrl+Q VS search only via LSP in ever scenario. serverCapabilities.WorkspaceSymbolProvider = true; diff --git a/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs index d08e0b5fe7d33..0b0e2db7167b8 100644 --- a/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs @@ -143,8 +143,9 @@ protected sealed override async Task ProduceTagsAsync( if (outliningService == null) return; + var options = BlockStructureOptions.From(document.Project); var blockStructure = await outliningService.GetBlockStructureAsync( - documentSnapshotSpan.Document, cancellationToken).ConfigureAwait(false); + documentSnapshotSpan.Document, options, cancellationToken).ConfigureAwait(false); ProcessSpans( context, documentSnapshotSpan.SnapshotSpan, outliningService, diff --git a/src/EditorFeatures/Test/Structure/BlockStructureServiceTests.cs b/src/EditorFeatures/Test/Structure/BlockStructureServiceTests.cs index ece4a2307e221..99919f10f2e7b 100644 --- a/src/EditorFeatures/Test/Structure/BlockStructureServiceTests.cs +++ b/src/EditorFeatures/Test/Structure/BlockStructureServiceTests.cs @@ -97,8 +97,9 @@ private static async Task> GetSpansFromWorkspaceAsync( var hostDocument = workspace.Documents.First(); var document = workspace.CurrentSolution.GetDocument(hostDocument.Id); var outliningService = document.GetLanguageService(); + var options = BlockStructureOptions.From(document.Project); - var structure = await outliningService.GetBlockStructureAsync(document, CancellationToken.None); + var structure = await outliningService.GetBlockStructureAsync(document, options, CancellationToken.None); return structure.Spans; } } diff --git a/src/EditorFeatures/VisualBasic/ImplementAbstractClass/ImplementAbstractClassCommandHandler.vb b/src/EditorFeatures/VisualBasic/ImplementAbstractClass/ImplementAbstractClassCommandHandler.vb index 4796e2a8ed7f6..b2904e3fca185 100644 --- a/src/EditorFeatures/VisualBasic/ImplementAbstractClass/ImplementAbstractClassCommandHandler.vb +++ b/src/EditorFeatures/VisualBasic/ImplementAbstractClass/ImplementAbstractClassCommandHandler.vb @@ -7,6 +7,7 @@ Imports System.Diagnostics.CodeAnalysis Imports System.Threading Imports Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities.CommandHandlers Imports Microsoft.CodeAnalysis.ImplementAbstractClass +Imports Microsoft.CodeAnalysis.ImplementType Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.VisualStudio.Commanding @@ -32,21 +33,22 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.ImplementAbstractClass Protected Overrides Function TryGetNewDocument( document As Document, - typeSyntax As TypeSyntax, + options As ImplementTypeOptions, + TypeSyntax As TypeSyntax, cancellationToken As CancellationToken ) As Document - If typeSyntax.Parent.Kind <> SyntaxKind.InheritsStatement Then + If TypeSyntax.Parent.Kind <> SyntaxKind.InheritsStatement Then Return Nothing End If - Dim classBlock = TryCast(typeSyntax.Parent.Parent, ClassBlockSyntax) + Dim classBlock = TryCast(TypeSyntax.Parent.Parent, ClassBlockSyntax) If classBlock Is Nothing Then Return Nothing End If Dim updatedDocument = ImplementAbstractClassData.TryImplementAbstractClassAsync( - document, classBlock, classBlock.ClassStatement.Identifier, cancellationToken).WaitAndGetResult(cancellationToken) + document, classBlock, classBlock.ClassStatement.Identifier, options, cancellationToken).WaitAndGetResult(cancellationToken) If updatedDocument IsNot Nothing AndAlso updatedDocument.GetTextChangesAsync(document, cancellationToken).WaitAndGetResult(cancellationToken).Count = 0 Then Return Nothing diff --git a/src/EditorFeatures/VisualBasic/ImplementInterface/ImplementInterfaceCommandHandler.vb b/src/EditorFeatures/VisualBasic/ImplementInterface/ImplementInterfaceCommandHandler.vb index d89a89fdf71e9..a93f14150b908 100644 --- a/src/EditorFeatures/VisualBasic/ImplementInterface/ImplementInterfaceCommandHandler.vb +++ b/src/EditorFeatures/VisualBasic/ImplementInterface/ImplementInterfaceCommandHandler.vb @@ -7,6 +7,7 @@ Imports System.Diagnostics.CodeAnalysis Imports System.Threading Imports Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities.CommandHandlers Imports Microsoft.CodeAnalysis.ImplementInterface +Imports Microsoft.CodeAnalysis.ImplementType Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.VisualStudio.Commanding @@ -32,6 +33,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.ImplementInterface Protected Overrides Function TryGetNewDocument( document As Document, + options As ImplementTypeOptions, typeSyntax As TypeSyntax, cancellationToken As CancellationToken ) As Document @@ -43,6 +45,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.ImplementInterface Dim service = document.GetLanguageService(Of IImplementInterfaceService)() Dim updatedDocument = service.ImplementInterfaceAsync( document, + options, typeSyntax.Parent, cancellationToken).WaitAndGetResult(cancellationToken) If updatedDocument.GetTextChangesAsync(document, cancellationToken).WaitAndGetResult(cancellationToken).Count = 0 Then diff --git a/src/EditorFeatures/VisualBasic/Utilities/CommandHandlers/AbstractImplementAbstractClassOrInterfaceCommandHandler.vb b/src/EditorFeatures/VisualBasic/Utilities/CommandHandlers/AbstractImplementAbstractClassOrInterfaceCommandHandler.vb index 275b1febc61c0..3c7167f549ea6 100644 --- a/src/EditorFeatures/VisualBasic/Utilities/CommandHandlers/AbstractImplementAbstractClassOrInterfaceCommandHandler.vb +++ b/src/EditorFeatures/VisualBasic/Utilities/CommandHandlers/AbstractImplementAbstractClassOrInterfaceCommandHandler.vb @@ -6,6 +6,7 @@ Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Editor.Implementation.EndConstructGeneration Imports Microsoft.CodeAnalysis.Formatting +Imports Microsoft.CodeAnalysis.ImplementType Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Simplification Imports Microsoft.CodeAnalysis.Text @@ -38,6 +39,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities.CommandHandlers Protected MustOverride Overloads Function TryGetNewDocument( document As Document, + options As ImplementTypeOptions, typeSyntax As TypeSyntax, cancellationToken As CancellationToken) As Document @@ -104,14 +106,6 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities.CommandHandlers End Function Private Overloads Function TryExecute(args As ReturnKeyCommandArgs, cancellationToken As CancellationToken) As Boolean - Dim textSnapshot = args.SubjectBuffer.CurrentSnapshot - Dim text = textSnapshot.AsText() - - Dim document = textSnapshot.GetOpenDocumentInCurrentContextWithChanges() - If document Is Nothing Then - Return False - End If - If Not _globalOptions.GetOption(FeatureOnOffOptions.AutomaticInsertionOfAbstractOrInterfaceMembers, LanguageNames.VisualBasic) Then Return False End If @@ -126,8 +120,15 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities.CommandHandlers Return False End If + Dim textSnapshot = args.SubjectBuffer.CurrentSnapshot + Dim document = textSnapshot.GetOpenDocumentInCurrentContextWithChanges() + If document Is Nothing Then + Return False + End If + Dim syntaxRoot = document.GetSyntaxRootSynchronously(cancellationToken) Dim token = syntaxRoot.FindTokenOnLeftOfPosition(caretPosition) + Dim text = textSnapshot.AsText() If text.Lines.IndexOf(token.SpanStart) <> text.Lines.IndexOf(caretPosition) Then Return False @@ -162,7 +163,8 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities.CommandHandlers Return False End If - Dim newDocument = TryGetNewDocument(document, identifier, cancellationToken) + Dim options = ImplementTypeOptions.From(document.Project) + Dim newDocument = TryGetNewDocument(document, options, identifier, cancellationToken) If newDocument Is Nothing Then Return False diff --git a/src/EditorFeatures/VisualBasicTest/ImplementAbstractClass/ImplementAbstractClassTests.vb b/src/EditorFeatures/VisualBasicTest/ImplementAbstractClass/ImplementAbstractClassTests.vb index b013da74084b6..74bd618c3f1b0 100644 --- a/src/EditorFeatures/VisualBasicTest/ImplementAbstractClass/ImplementAbstractClassTests.vb +++ b/src/EditorFeatures/VisualBasicTest/ImplementAbstractClass/ImplementAbstractClassTests.vb @@ -631,7 +631,7 @@ Class C End Set End Property End Class", parameters:=New TestParameters(options:=[Option]( - ImplementTypeOptions.PropertyGenerationBehavior, + ImplementTypeOptions.Metadata.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties))) End Function End Class diff --git a/src/EditorFeatures/VisualBasicTest/ImplementInterface/ImplementInterfaceTests.vb b/src/EditorFeatures/VisualBasicTest/ImplementInterface/ImplementInterfaceTests.vb index 235274b2ef184..f915762663037 100644 --- a/src/EditorFeatures/VisualBasicTest/ImplementInterface/ImplementInterfaceTests.vb +++ b/src/EditorFeatures/VisualBasicTest/ImplementInterface/ImplementInterfaceTests.vb @@ -4647,7 +4647,7 @@ class Class End Set End Property end class", parameters:=New TestParameters(options:=[Option]( - ImplementTypeOptions.PropertyGenerationBehavior, + ImplementTypeOptions.Metadata.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties))) End Function End Class diff --git a/src/EditorFeatures/VisualBasicTest/Structure/MetadataAsSource/InvalidIdentifierStructureTests.vb b/src/EditorFeatures/VisualBasicTest/Structure/MetadataAsSource/InvalidIdentifierStructureTests.vb index ed68b90fb82a6..bcbeef95fe486 100644 --- a/src/EditorFeatures/VisualBasicTest/Structure/MetadataAsSource/InvalidIdentifierStructureTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Structure/MetadataAsSource/InvalidIdentifierStructureTests.vb @@ -30,8 +30,9 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Outlining.Metadata Friend Overrides Async Function GetBlockSpansWorkerAsync(document As Document, position As Integer) As Task(Of ImmutableArray(Of BlockSpan)) Dim outliningService = document.GetLanguageService(Of BlockStructureService)() + Dim options = BlockStructureOptions.From(document.Project) - Return (Await outliningService.GetBlockStructureAsync(document, CancellationToken.None)).Spans + Return (Await outliningService.GetBlockStructureAsync(document, options, CancellationToken.None)).Spans End Function diff --git a/src/EditorFeatures/VisualBasicTest/Structure/OverallStructureTests.vb b/src/EditorFeatures/VisualBasicTest/Structure/OverallStructureTests.vb index fc63045eeba3c..28fc10b63a6fc 100644 --- a/src/EditorFeatures/VisualBasicTest/Structure/OverallStructureTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Structure/OverallStructureTests.vb @@ -19,8 +19,9 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Outlining Friend Overrides Async Function GetBlockSpansWorkerAsync(document As Document, position As Integer) As Task(Of ImmutableArray(Of BlockSpan)) Dim outliningService = document.GetLanguageService(Of BlockStructureService)() + Dim options = BlockStructureOptions.From(document.Project) - Return (Await outliningService.GetBlockStructureAsync(document, CancellationToken.None)).Spans + Return (Await outliningService.GetBlockStructureAsync(document, options, CancellationToken.None)).Spans End Function diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/ExpressionCompilerTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/ExpressionCompilerTests.cs index 4276cb3848c78..ba43e33d58758 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/ExpressionCompilerTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/ExpressionCompilerTests.cs @@ -1494,8 +1494,10 @@ int WP { set { } } int this[int x, int y] { set { } } int this[int x, int y, int z] { get { return 0; } set { } } - int M() { return 0; } - } + int M1() { return 0; } + int M2() { return 0; } + int M2(int i) { return i; } +} "; var compilation0 = CreateCompilation( source, @@ -1504,7 +1506,7 @@ int WP { set { } } WithRuntimeInstance(compilation0, runtime => { - var context = CreateMethodContext(runtime, "C.M"); + var context = CreateMethodContext(runtime, "C.M1"); CheckResultFlags(context, "F", DkmClrCompilationResultFlags.None); CheckResultFlags(context, "RF", DkmClrCompilationResultFlags.ReadOnlyResult); @@ -1522,11 +1524,12 @@ int WP { set { } } CheckResultFlags(context, "this[1, 2]", DkmClrCompilationResultFlags.None, "error CS0154: The property or indexer 'C.this[int, int]' cannot be used in this context because it lacks the get accessor"); CheckResultFlags(context, "this[1, 2, 3]", DkmClrCompilationResultFlags.None); - CheckResultFlags(context, "M()", DkmClrCompilationResultFlags.PotentialSideEffect | DkmClrCompilationResultFlags.ReadOnlyResult); + CheckResultFlags(context, "M1()", DkmClrCompilationResultFlags.PotentialSideEffect | DkmClrCompilationResultFlags.ReadOnlyResult); CheckResultFlags(context, "null", DkmClrCompilationResultFlags.ReadOnlyResult); CheckResultFlags(context, "1", DkmClrCompilationResultFlags.ReadOnlyResult); - CheckResultFlags(context, "M", DkmClrCompilationResultFlags.None, "error CS0428: Cannot convert method group 'M' to non-delegate type 'object'. Did you intend to invoke the method?"); + CheckResultFlags(context, "M1", DkmClrCompilationResultFlags.ReadOnlyResult); + CheckResultFlags(context, "M2", DkmClrCompilationResultFlags.None, "error CS8917: The delegate type could not be inferred."); CheckResultFlags(context, "typeof(C)", DkmClrCompilationResultFlags.ReadOnlyResult); CheckResultFlags(context, "new C()", DkmClrCompilationResultFlags.ReadOnlyResult); }); @@ -1687,20 +1690,43 @@ public void EvaluateMethodGroup() var source = @"class C { - void M() + void M1() { } + void M2() { } + void M2(int i) { } }"; ResultProperties resultProperties; string error; var testData = Evaluate( source, OutputKind.DynamicallyLinkedLibrary, - methodName: "C.M", - expr: "this.M", + methodName: "C.M1", + expr: "this.M2", resultProperties: out resultProperties, error: out error); - Assert.Equal("error CS0428: Cannot convert method group 'M' to non-delegate type 'object'. Did you intend to invoke the method?", error); + Assert.Equal("error CS8917: The delegate type could not be inferred.", error); + + testData = Evaluate( + source, + OutputKind.DynamicallyLinkedLibrary, + methodName: "C.M1", + expr: "this.M1", + resultProperties: out resultProperties, + error: out error); + Assert.Equal(DkmClrCompilationResultFlags.ReadOnlyResult, resultProperties.Flags); + var methodData = testData.GetMethodData("<>x.<>m0"); + var method = (MethodSymbol)methodData.Method; + Assert.Equal(SpecialType.System_Object, method.ReturnType.SpecialType); + methodData.VerifyIL( +@"{ + // Code size 13 (0xd) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldftn ""void C.M1()"" + IL_0007: newobj ""System.Action..ctor(object, System.IntPtr)"" + IL_000c: ret +}"); } [Fact] @@ -1709,25 +1735,44 @@ public void AssignMethodGroup() var source = @"class C { - static void M() + static void M1() { object o; } + static void M2() { } + static void M2(int i) { } }"; var compilation0 = CreateCompilation(source, options: TestOptions.DebugDll); WithRuntimeInstance(compilation0, runtime => { var context = CreateMethodContext( runtime, - methodName: "C.M"); + methodName: "C.M1"); string error; var testData = new CompilationTestData(); var result = context.CompileAssignment( target: "o", - expr: "M", + expr: "M2", error: out error, testData: testData); - Assert.Equal("error CS0428: Cannot convert method group 'M' to non-delegate type 'object'. Did you intend to invoke the method?", error); + Assert.Equal("error CS8917: The delegate type could not be inferred.", error); + testData = new CompilationTestData(); + context.CompileAssignment( + target: "o", + expr: "M1", + error: out error, + testData: testData); + testData.GetMethodData("<>x.<>m0").VerifyIL( +@"{ + // Code size 14 (0xe) + .maxstack 2 + .locals init (object V_0) //o + IL_0000: ldnull + IL_0001: ldftn ""void C.M1()"" + IL_0007: newobj ""System.Action..ctor(object, System.IntPtr)"" + IL_000c: stloc.0 + IL_000d: ret +}"); }); } diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/ResultPropertiesTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/ResultPropertiesTests.cs index 6cd5cbfbf9e49..dd00fc3579d04 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/ResultPropertiesTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/ResultPropertiesTests.cs @@ -377,6 +377,8 @@ class C void Test() { } + static void F() { } + static void F(int i) { } } "; var comp = CreateCompilation(source, options: TestOptions.DebugDll); @@ -385,7 +387,7 @@ void Test() var context = CreateMethodContext(runtime, methodName: "C.Test"); VerifyErrorResultProperties(context, "x => x"); - VerifyErrorResultProperties(context, "Test"); + VerifyErrorResultProperties(context, "F"); VerifyErrorResultProperties(context, "Missing"); VerifyErrorResultProperties(context, "C"); }); diff --git a/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceCodeFixProvider.cs b/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceCodeFixProvider.cs index 89c738968cc5f..ffaa2974eb11c 100644 --- a/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceCodeFixProvider.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.ImplementInterface; +using Microsoft.CodeAnalysis.ImplementType; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CSharp.ImplementInterface @@ -50,10 +51,11 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) var service = document.GetRequiredLanguageService(); var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var options = ImplementTypeOptions.From(document.Project); var actions = token.Parent.GetAncestorsOrThis() .Where(_interfaceName) - .Select(n => service.GetCodeActions(document, model, n, cancellationToken)) + .Select(n => service.GetCodeActions(document, options, model, n, cancellationToken)) .FirstOrDefault(a => !a.IsEmpty); if (actions.IsDefaultOrEmpty) diff --git a/src/Features/Core/Portable/ImplementAbstractClass/AbstractImplementAbstractClassCodeFixProvider.cs b/src/Features/Core/Portable/ImplementAbstractClass/AbstractImplementAbstractClassCodeFixProvider.cs index ae201af95dac2..6295abf449ee6 100644 --- a/src/Features/Core/Portable/ImplementAbstractClass/AbstractImplementAbstractClassCodeFixProvider.cs +++ b/src/Features/Core/Portable/ImplementAbstractClass/AbstractImplementAbstractClassCodeFixProvider.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.ImplementType; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.ImplementAbstractClass @@ -40,8 +41,9 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) if (classNode == null) return; + var options = ImplementTypeOptions.From(document.Project); var data = await ImplementAbstractClassData.TryGetDataAsync( - document, classNode, GetClassIdentifier(classNode), cancellationToken).ConfigureAwait(false); + document, classNode, GetClassIdentifier(classNode), options, cancellationToken).ConfigureAwait(false); if (data == null) return; diff --git a/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs b/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs index 1ba4b7426c990..fd78fc5a477c8 100644 --- a/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs +++ b/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs @@ -21,9 +21,10 @@ namespace Microsoft.CodeAnalysis.ImplementAbstractClass { - internal class ImplementAbstractClassData + internal sealed class ImplementAbstractClassData { private readonly Document _document; + private readonly ImplementTypeOptions _options; private readonly SyntaxNode _classNode; private readonly SyntaxToken _classIdentifier; private readonly ImmutableArray<(INamedTypeSymbol type, ImmutableArray members)> _unimplementedMembers; @@ -32,11 +33,12 @@ internal class ImplementAbstractClassData public readonly INamedTypeSymbol AbstractClassType; public ImplementAbstractClassData( - Document document, SyntaxNode classNode, SyntaxToken classIdentifier, + Document document, ImplementTypeOptions options, SyntaxNode classNode, SyntaxToken classIdentifier, INamedTypeSymbol classType, INamedTypeSymbol abstractClassType, ImmutableArray<(INamedTypeSymbol type, ImmutableArray members)> unimplementedMembers) { _document = document; + _options = options; _classNode = classNode; _classIdentifier = classIdentifier; ClassType = classType; @@ -45,7 +47,7 @@ public ImplementAbstractClassData( } public static async Task TryGetDataAsync( - Document document, SyntaxNode classNode, SyntaxToken classIdentifier, CancellationToken cancellationToken) + Document document, SyntaxNode classNode, SyntaxToken classIdentifier, ImplementTypeOptions options, CancellationToken cancellationToken) { var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (semanticModel.GetDeclaredSymbol(classNode, cancellationToken) is not INamedTypeSymbol classType) @@ -70,14 +72,14 @@ public ImplementAbstractClassData( return null; return new ImplementAbstractClassData( - document, classNode, classIdentifier, + document, options, classNode, classIdentifier, classType, abstractClassType, unimplementedMembers); } public static async Task TryImplementAbstractClassAsync( - Document document, SyntaxNode classNode, SyntaxToken classIdentifier, CancellationToken cancellationToken) + Document document, SyntaxNode classNode, SyntaxToken classIdentifier, ImplementTypeOptions options, CancellationToken cancellationToken) { - var data = await TryGetDataAsync(document, classNode, classIdentifier, cancellationToken).ConfigureAwait(false); + var data = await TryGetDataAsync(document, classNode, classIdentifier, options, cancellationToken).ConfigureAwait(false); if (data == null) return null; @@ -88,14 +90,8 @@ public async Task ImplementAbstractClassAsync( ISymbol? throughMember, bool? canDelegateAllMembers, CancellationToken cancellationToken) { var compilation = await _document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - - var options = await _document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); - var propertyGenerationBehavior = options.GetOption(ImplementTypeOptions.PropertyGenerationBehavior); - - var memberDefinitions = GenerateMembers(compilation, throughMember, propertyGenerationBehavior, cancellationToken); - - var insertionBehavior = options.GetOption(ImplementTypeOptions.InsertionBehavior); - var groupMembers = insertionBehavior == ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind; + var memberDefinitions = GenerateMembers(compilation, throughMember, _options.PropertyGenerationBehavior, cancellationToken); + var groupMembers = _options.InsertionBehavior == ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind; // If we're implementing through one of our members, but we can't delegate all members // through it, then give an error message on the class decl letting the user know. @@ -109,6 +105,7 @@ public async Task ImplementAbstractClassAsync( FeaturesResources.Base_classes_contain_inaccessible_unimplemented_members))); } + var options = await _document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); var updatedClassNode = CodeGenerator.AddMemberDeclarations( classNodeToAddMembersTo, memberDefinitions, diff --git a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs index 8d308dfc95efa..8b1dce2749de2 100644 --- a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs +++ b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.CodeAction.cs @@ -32,6 +32,7 @@ internal partial class ImplementInterfaceCodeAction : CodeAction private readonly bool _onlyRemaining; protected readonly ISymbol ThroughMember; protected readonly Document Document; + protected readonly ImplementTypeOptions Options; protected readonly State State; protected readonly AbstractImplementInterfaceService Service; private readonly string _equivalenceKey; @@ -39,6 +40,7 @@ internal partial class ImplementInterfaceCodeAction : CodeAction internal ImplementInterfaceCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state, bool explicitly, bool abstractly, @@ -47,6 +49,7 @@ internal ImplementInterfaceCodeAction( { Service = service; Document = document; + Options = options; State = state; Abstractly = abstractly; _onlyRemaining = onlyRemaining; @@ -58,42 +61,47 @@ internal ImplementInterfaceCodeAction( public static ImplementInterfaceCodeAction CreateImplementAbstractlyCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state) { - return new ImplementInterfaceCodeAction(service, document, state, explicitly: false, abstractly: true, onlyRemaining: true, throughMember: null); + return new ImplementInterfaceCodeAction(service, document, options, state, explicitly: false, abstractly: true, onlyRemaining: true, throughMember: null); } public static ImplementInterfaceCodeAction CreateImplementCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state) { - return new ImplementInterfaceCodeAction(service, document, state, explicitly: false, abstractly: false, onlyRemaining: true, throughMember: null); + return new ImplementInterfaceCodeAction(service, document, options, state, explicitly: false, abstractly: false, onlyRemaining: true, throughMember: null); } public static ImplementInterfaceCodeAction CreateImplementExplicitlyCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state) { - return new ImplementInterfaceCodeAction(service, document, state, explicitly: true, abstractly: false, onlyRemaining: false, throughMember: null); + return new ImplementInterfaceCodeAction(service, document, options, state, explicitly: true, abstractly: false, onlyRemaining: false, throughMember: null); } public static ImplementInterfaceCodeAction CreateImplementThroughMemberCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state, ISymbol throughMember) { - return new ImplementInterfaceCodeAction(service, document, state, explicitly: false, abstractly: false, onlyRemaining: false, throughMember: throughMember); + return new ImplementInterfaceCodeAction(service, document, options, state, explicitly: false, abstractly: false, onlyRemaining: false, throughMember: throughMember); } public static ImplementInterfaceCodeAction CreateImplementRemainingExplicitlyCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state) { - return new ImplementInterfaceCodeAction(service, document, state, explicitly: true, abstractly: false, onlyRemaining: true, throughMember: null); + return new ImplementInterfaceCodeAction(service, document, options, state, explicitly: true, abstractly: false, onlyRemaining: true, throughMember: null); } public override string Title @@ -189,18 +197,15 @@ protected async Task GetUpdatedDocumentAsync( var compilation = await result.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); var isComImport = unimplementedMembers.Any(t => t.type.IsComImport); - var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); - var propertyGenerationBehavior = options.GetOption(ImplementTypeOptions.PropertyGenerationBehavior); var memberDefinitions = GenerateMembers( - compilation, unimplementedMembers, propertyGenerationBehavior); + compilation, unimplementedMembers, Options.PropertyGenerationBehavior); // Only group the members in the destination if the user wants that *and* // it's not a ComImport interface. Member ordering in ComImport interfaces // matters, so we don't want to much with them. - var insertionBehavior = options.GetOption(ImplementTypeOptions.InsertionBehavior); var groupMembers = !isComImport && - insertionBehavior == ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind; + Options.InsertionBehavior == ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind; return await CodeGenerator.AddMemberDeclarationsAsync( result.Project.Solution, classOrStructType, diff --git a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.DisposePatternCodeAction.cs b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.DisposePatternCodeAction.cs index c23cdfc4759c7..ccc4517226375 100644 --- a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.DisposePatternCodeAction.cs +++ b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.DisposePatternCodeAction.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.ImplementType; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -88,27 +89,30 @@ private class ImplementInterfaceWithDisposePatternCodeAction : ImplementInterfac public ImplementInterfaceWithDisposePatternCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state, bool explicitly, bool abstractly, - ISymbol? throughMember) : base(service, document, state, explicitly, abstractly, onlyRemaining: !explicitly, throughMember) + ISymbol? throughMember) : base(service, document, options, state, explicitly, abstractly, onlyRemaining: !explicitly, throughMember) { } public static ImplementInterfaceWithDisposePatternCodeAction CreateImplementWithDisposePatternCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state) { - return new ImplementInterfaceWithDisposePatternCodeAction(service, document, state, explicitly: false, abstractly: false, throughMember: null); + return new ImplementInterfaceWithDisposePatternCodeAction(service, document, options, state, explicitly: false, abstractly: false, throughMember: null); } public static ImplementInterfaceWithDisposePatternCodeAction CreateImplementExplicitlyWithDisposePatternCodeAction( AbstractImplementInterfaceService service, Document document, + ImplementTypeOptions options, State state) { - return new ImplementInterfaceWithDisposePatternCodeAction(service, document, state, explicitly: true, abstractly: false, throughMember: null); + return new ImplementInterfaceWithDisposePatternCodeAction(service, document, options, state, explicitly: true, abstractly: false, throughMember: null); } public override string Title diff --git a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.cs b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.cs index fec05251bb2e4..b551def32576f 100644 --- a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.cs +++ b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceService.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.ImplementType; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -35,7 +36,7 @@ protected AbstractImplementInterfaceService() protected abstract SyntaxNode AddCommentInsideIfStatement(SyntaxNode ifDisposingStatement, SyntaxTriviaList trivia); protected abstract SyntaxNode CreateFinalizer(SyntaxGenerator generator, INamedTypeSymbol classType, string disposeMethodDisplayString); - public async Task ImplementInterfaceAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) + public async Task ImplementInterfaceAsync(Document document, ImplementTypeOptions options, SyntaxNode node, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Refactoring_ImplementInterface, cancellationToken)) { @@ -49,20 +50,20 @@ public async Task ImplementInterfaceAsync(Document document, SyntaxNod // While implementing just one default action, like in the case of pressing enter after interface name in VB, // choose to implement with the dispose pattern as that's the Dev12 behavior. var action = ShouldImplementDisposePattern(state, explicitly: false) - ? ImplementInterfaceWithDisposePatternCodeAction.CreateImplementWithDisposePatternCodeAction(this, document, state) - : ImplementInterfaceCodeAction.CreateImplementCodeAction(this, document, state); + ? ImplementInterfaceWithDisposePatternCodeAction.CreateImplementWithDisposePatternCodeAction(this, document, options, state) + : ImplementInterfaceCodeAction.CreateImplementCodeAction(this, document, options, state); return await action.GetUpdatedDocumentAsync(cancellationToken).ConfigureAwait(false); } } - public ImmutableArray GetCodeActions(Document document, SemanticModel model, SyntaxNode node, CancellationToken cancellationToken) + public ImmutableArray GetCodeActions(Document document, ImplementTypeOptions options, SemanticModel model, SyntaxNode node, CancellationToken cancellationToken) { var state = State.Generate(this, document, model, node, cancellationToken); - return GetActions(document, state).ToImmutableArray(); + return GetActions(document, options, state).ToImmutableArray(); } - private IEnumerable GetActions(Document document, State state) + private IEnumerable GetActions(Document document, ImplementTypeOptions options, State state) { if (state == null) { @@ -71,38 +72,38 @@ private IEnumerable GetActions(Document document, State state) if (state.MembersWithoutExplicitOrImplicitImplementationWhichCanBeImplicitlyImplemented.Length > 0) { - yield return ImplementInterfaceCodeAction.CreateImplementCodeAction(this, document, state); + yield return ImplementInterfaceCodeAction.CreateImplementCodeAction(this, document, options, state); if (ShouldImplementDisposePattern(state, explicitly: false)) { - yield return ImplementInterfaceWithDisposePatternCodeAction.CreateImplementWithDisposePatternCodeAction(this, document, state); + yield return ImplementInterfaceWithDisposePatternCodeAction.CreateImplementWithDisposePatternCodeAction(this, document, options, state); } var delegatableMembers = GetDelegatableMembers(state); foreach (var member in delegatableMembers) { - yield return ImplementInterfaceCodeAction.CreateImplementThroughMemberCodeAction(this, document, state, member); + yield return ImplementInterfaceCodeAction.CreateImplementThroughMemberCodeAction(this, document, options, state, member); } if (state.ClassOrStructType.IsAbstract) { - yield return ImplementInterfaceCodeAction.CreateImplementAbstractlyCodeAction(this, document, state); + yield return ImplementInterfaceCodeAction.CreateImplementAbstractlyCodeAction(this, document, options, state); } } if (state.MembersWithoutExplicitImplementation.Length > 0) { - yield return ImplementInterfaceCodeAction.CreateImplementExplicitlyCodeAction(this, document, state); + yield return ImplementInterfaceCodeAction.CreateImplementExplicitlyCodeAction(this, document, options, state); if (ShouldImplementDisposePattern(state, explicitly: true)) { - yield return ImplementInterfaceWithDisposePatternCodeAction.CreateImplementExplicitlyWithDisposePatternCodeAction(this, document, state); + yield return ImplementInterfaceWithDisposePatternCodeAction.CreateImplementExplicitlyWithDisposePatternCodeAction(this, document, options, state); } } if (AnyImplementedImplicitly(state)) { - yield return ImplementInterfaceCodeAction.CreateImplementRemainingExplicitlyCodeAction(this, document, state); + yield return ImplementInterfaceCodeAction.CreateImplementRemainingExplicitlyCodeAction(this, document, options, state); } } diff --git a/src/Features/Core/Portable/ImplementInterface/IImplementInterfaceService.cs b/src/Features/Core/Portable/ImplementInterface/IImplementInterfaceService.cs index 6d740d31ddabf..6d572fbbcfd53 100644 --- a/src/Features/Core/Portable/ImplementInterface/IImplementInterfaceService.cs +++ b/src/Features/Core/Portable/ImplementInterface/IImplementInterfaceService.cs @@ -9,12 +9,13 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.ImplementType; namespace Microsoft.CodeAnalysis.ImplementInterface { internal interface IImplementInterfaceService : ILanguageService { - Task ImplementInterfaceAsync(Document document, SyntaxNode node, CancellationToken cancellationToken); - ImmutableArray GetCodeActions(Document document, SemanticModel model, SyntaxNode node, CancellationToken cancellationToken); + Task ImplementInterfaceAsync(Document document, ImplementTypeOptions options, SyntaxNode node, CancellationToken cancellationToken); + ImmutableArray GetCodeActions(Document document, ImplementTypeOptions options, SemanticModel model, SyntaxNode node, CancellationToken cancellationToken); } } diff --git a/src/Features/Core/Portable/ImplementType/ImplementTypeInsertionBehavior.cs b/src/Features/Core/Portable/ImplementType/ImplementTypeInsertionBehavior.cs new file mode 100644 index 0000000000000..c88ea79f906e9 --- /dev/null +++ b/src/Features/Core/Portable/ImplementType/ImplementTypeInsertionBehavior.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.ImplementType +{ + internal enum ImplementTypeInsertionBehavior + { + WithOtherMembersOfTheSameKind = 0, + AtTheEnd = 1, + } +} diff --git a/src/Features/Core/Portable/ImplementType/ImplementTypeOptions.cs b/src/Features/Core/Portable/ImplementType/ImplementTypeOptions.cs index 25799f248a500..2a543b075ce87 100644 --- a/src/Features/Core/Portable/ImplementType/ImplementTypeOptions.cs +++ b/src/Features/Core/Portable/ImplementType/ImplementTypeOptions.cs @@ -2,39 +2,53 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; +using System.Composition; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options.Providers; namespace Microsoft.CodeAnalysis.ImplementType { - internal enum ImplementTypeInsertionBehavior + internal readonly record struct ImplementTypeOptions( + ImplementTypeInsertionBehavior InsertionBehavior, + ImplementTypePropertyGenerationBehavior PropertyGenerationBehavior) { - WithOtherMembersOfTheSameKind = 0, - AtTheEnd = 1, - } + public static ImplementTypeOptions From(Project project) + => From(project.Solution.Options, project.Language); - internal enum ImplementTypePropertyGenerationBehavior - { - PreferThrowingProperties = 0, - PreferAutoProperties = 1, - } + public static ImplementTypeOptions From(OptionSet options, string language) + => new( + InsertionBehavior: options.GetOption(Metadata.InsertionBehavior, language), + PropertyGenerationBehavior: options.GetOption(Metadata.PropertyGenerationBehavior, language)); - internal static class ImplementTypeOptions - { - public static readonly PerLanguageOption2 InsertionBehavior = - new( - nameof(ImplementTypeOptions), - nameof(InsertionBehavior), - defaultValue: ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind, - storageLocation: new RoamingProfileStorageLocation( - $"TextEditor.%LANGUAGE%.{nameof(ImplementTypeOptions)}.{nameof(InsertionBehavior)}")); - - public static readonly PerLanguageOption2 PropertyGenerationBehavior = - new( - nameof(ImplementTypeOptions), - nameof(PropertyGenerationBehavior), - defaultValue: ImplementTypePropertyGenerationBehavior.PreferThrowingProperties, - storageLocation: new RoamingProfileStorageLocation( - $"TextEditor.%LANGUAGE%.{nameof(ImplementTypeOptions)}.{nameof(PropertyGenerationBehavior)}")); + [ExportSolutionOptionProvider, Shared] + internal sealed class Metadata : IOptionProvider + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public Metadata() + { + } + + public ImmutableArray Options { get; } = ImmutableArray.Create( + InsertionBehavior, + PropertyGenerationBehavior); + + private const string FeatureName = "ImplementTypeOptions"; + + public static readonly PerLanguageOption2 InsertionBehavior = + new(FeatureName, + "InsertionBehavior", + defaultValue: ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind, + storageLocation: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.ImplementTypeOptions.InsertionBehavior")); + public static readonly PerLanguageOption2 PropertyGenerationBehavior = + new(FeatureName, + "PropertyGenerationBehavior", + defaultValue: ImplementTypePropertyGenerationBehavior.PreferThrowingProperties, + storageLocation: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.ImplementTypeOptions.PropertyGenerationBehavior")); + } } } diff --git a/src/Features/Core/Portable/ImplementType/ImplementTypeOptionsProvider.cs b/src/Features/Core/Portable/ImplementType/ImplementTypeOptionsProvider.cs deleted file mode 100644 index 937942765e476..0000000000000 --- a/src/Features/Core/Portable/ImplementType/ImplementTypeOptionsProvider.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Options.Providers; - -namespace Microsoft.CodeAnalysis.ImplementType -{ - [ExportSolutionOptionProvider, Shared] - internal class ImplementTypeOptionsProvider : IOptionProvider - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public ImplementTypeOptionsProvider() - { - } - - public ImmutableArray Options { get; } = ImmutableArray.Create( - ImplementTypeOptions.InsertionBehavior); - } -} diff --git a/src/Features/Core/Portable/ImplementType/ImplementTypePropertyGenerationBehavior.cs b/src/Features/Core/Portable/ImplementType/ImplementTypePropertyGenerationBehavior.cs new file mode 100644 index 0000000000000..525c2f8f2ca5e --- /dev/null +++ b/src/Features/Core/Portable/ImplementType/ImplementTypePropertyGenerationBehavior.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.ImplementType +{ + internal enum ImplementTypePropertyGenerationBehavior + { + PreferThrowingProperties = 0, + PreferAutoProperties = 1, + } +} diff --git a/src/Features/Core/Portable/Structure/BlockStructureOptions.cs b/src/Features/Core/Portable/Structure/BlockStructureOptions.cs index a952ea35c3539..7cfed666438f4 100644 --- a/src/Features/Core/Portable/Structure/BlockStructureOptions.cs +++ b/src/Features/Core/Portable/Structure/BlockStructureOptions.cs @@ -22,6 +22,17 @@ internal record struct BlockStructureOptions( int MaximumBannerLength, bool IsMetadataAsSource) { + public static readonly BlockStructureOptions Default = + new(ShowBlockStructureGuidesForCommentsAndPreprocessorRegions: Metadata.ShowBlockStructureGuidesForCommentsAndPreprocessorRegions.DefaultValue, + ShowBlockStructureGuidesForDeclarationLevelConstructs: Metadata.ShowBlockStructureGuidesForDeclarationLevelConstructs.DefaultValue, + ShowBlockStructureGuidesForCodeLevelConstructs: Metadata.ShowBlockStructureGuidesForCodeLevelConstructs.DefaultValue, + ShowOutliningForCommentsAndPreprocessorRegions: Metadata.ShowOutliningForCommentsAndPreprocessorRegions.DefaultValue, + ShowOutliningForDeclarationLevelConstructs: Metadata.ShowOutliningForDeclarationLevelConstructs.DefaultValue, + ShowOutliningForCodeLevelConstructs: Metadata.ShowOutliningForCodeLevelConstructs.DefaultValue, + CollapseRegionsWhenCollapsingToDefinitions: Metadata.CollapseRegionsWhenCollapsingToDefinitions.DefaultValue, + MaximumBannerLength: Metadata.MaximumBannerLength.DefaultValue, + IsMetadataAsSource: false); + public static BlockStructureOptions From(Project project) => From(project.Solution.Options, project.Language, isMetadataAsSource: project.Solution.Workspace.Kind == WorkspaceKind.MetadataAsSource); diff --git a/src/Features/Core/Portable/Structure/BlockStructureService.cs b/src/Features/Core/Portable/Structure/BlockStructureService.cs index f0c5bd48f30b1..ceb7609380933 100644 --- a/src/Features/Core/Portable/Structure/BlockStructureService.cs +++ b/src/Features/Core/Portable/Structure/BlockStructureService.cs @@ -25,6 +25,6 @@ public static BlockStructureService GetService(Document document) /// public abstract string Language { get; } - public abstract Task GetBlockStructureAsync(Document document, CancellationToken cancellationToken); + public abstract Task GetBlockStructureAsync(Document document, BlockStructureOptions options, CancellationToken cancellationToken); } } diff --git a/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs b/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs index 7543d0317b84b..68d58fa6d0a96 100644 --- a/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs +++ b/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs @@ -47,9 +47,12 @@ private ImmutableArray GetImportedProviders() public override async Task GetBlockStructureAsync( Document document, + BlockStructureOptions options, CancellationToken cancellationToken) { - var context = await CreateContextAsync(document, cancellationToken).ConfigureAwait(false); + var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var context = CreateContext(syntaxTree, options, cancellationToken); + return GetBlockStructure(context, _providers); } @@ -62,13 +65,6 @@ public BlockStructure GetBlockStructure( return GetBlockStructure(context, _providers); } - private static async Task CreateContextAsync(Document document, CancellationToken cancellationToken) - { - var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var options = BlockStructureOptions.From(document.Project); - return CreateContext(syntaxTree, options, cancellationToken); - } - private static BlockStructureContext CreateContext( SyntaxTree syntaxTree, in BlockStructureOptions options, diff --git a/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs b/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs index 9b3cfad98143f..c35f190587167 100644 --- a/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs @@ -44,7 +44,8 @@ public FoldingRangesHandler() return Array.Empty(); } - var blockStructure = await blockStructureService.GetBlockStructureAsync(document, cancellationToken).ConfigureAwait(false); + var options = BlockStructureOptions.From(document.Project); + var blockStructure = await blockStructureService.GetBlockStructureAsync(document, options, cancellationToken).ConfigureAwait(false); if (blockStructure == null) { return Array.Empty(); diff --git a/src/Features/VisualBasic/Portable/ImplementInterface/VisualBasicImplementInterfaceCodeFixProvider.vb b/src/Features/VisualBasic/Portable/ImplementInterface/VisualBasicImplementInterfaceCodeFixProvider.vb index 4570e4732a071..4d4081405d9db 100644 --- a/src/Features/VisualBasic/Portable/ImplementInterface/VisualBasicImplementInterfaceCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/ImplementInterface/VisualBasicImplementInterfaceCodeFixProvider.vb @@ -7,6 +7,7 @@ Imports System.Composition Imports System.Diagnostics.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeFixes Imports Microsoft.CodeAnalysis.ImplementInterface +Imports Microsoft.CodeAnalysis.ImplementType Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ImplementInterface @@ -57,9 +58,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ImplementInterface Return End If + Dim options = ImplementTypeOptions.From(document.Project) Dim service = document.GetLanguageService(Of IImplementInterfaceService)() Dim actions = service.GetCodeActions( document, + options, Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False), typeNode, cancellationToken) diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Structure/FSharpBlockStructureService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Structure/FSharpBlockStructureService.cs index 588bc7fb239ef..5826b8fdda73d 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Structure/FSharpBlockStructureService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Structure/FSharpBlockStructureService.cs @@ -29,7 +29,7 @@ public FSharpBlockStructureService(IFSharpBlockStructureService service) public override string Language => LanguageNames.FSharp; - public override async Task GetBlockStructureAsync(Document document, CancellationToken cancellationToken) + public override async Task GetBlockStructureAsync(Document document, BlockStructureOptions options, CancellationToken cancellationToken) { var blockStructure = await _service.GetBlockStructureAsync(document, cancellationToken).ConfigureAwait(false); if (blockStructure != null) diff --git a/src/Tools/ExternalAccess/OmniSharp/Completion/OmniSharpCompletionOptions.cs b/src/Tools/ExternalAccess/OmniSharp/Completion/OmniSharpCompletionOptions.cs new file mode 100644 index 0000000000000..ff99d9b65b090 --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Completion/OmniSharpCompletionOptions.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Completion; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Completion +{ + internal readonly record struct OmniSharpCompletionOptions( + bool ShowItemsFromUnimportedNamespaces) + { + internal CompletionOptions ToCompletionOptions() + => CompletionOptions.Default with { ShowItemsFromUnimportedNamespaces = ShowItemsFromUnimportedNamespaces }; + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Completion/OmniSharpCompletionService.cs b/src/Tools/ExternalAccess/OmniSharp/Completion/OmniSharpCompletionService.cs index 7413e21d5d498..fb5c4d40c432b 100644 --- a/src/Tools/ExternalAccess/OmniSharp/Completion/OmniSharpCompletionService.cs +++ b/src/Tools/ExternalAccess/OmniSharp/Completion/OmniSharpCompletionService.cs @@ -7,24 +7,35 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Completion { internal static class OmniSharpCompletionService { - public static Task<(CompletionList? completionList, bool expandItemsAvailable)> GetCompletionsAsync( + public static async ValueTask ShouldTriggerCompletionAsync( this CompletionService completionService, Document document, int caretPosition, CompletionTrigger trigger, ImmutableHashSet? roles, + OmniSharpCompletionOptions options, CancellationToken cancellationToken) - => completionService.GetCompletionsInternalAsync(document, caretPosition, CompletionOptions.Default, trigger, roles, cancellationToken); + { + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + return completionService.ShouldTriggerCompletion(document.Project, document.Project.LanguageServices, text, caretPosition, trigger, options.ToCompletionOptions(), roles); + } - public static string? GetProviderName(this CompletionItem completionItem) => completionItem.ProviderName; + public static Task<(CompletionList? completionList, bool expandItemsAvailable)> GetCompletionsAsync( + this CompletionService completionService, + Document document, + int caretPosition, + CompletionTrigger trigger, + ImmutableHashSet? roles, + OmniSharpCompletionOptions options, + CancellationToken cancellationToken) + => completionService.GetCompletionsInternalAsync(document, caretPosition, options.ToCompletionOptions(), trigger, roles, cancellationToken); - public static bool? IncludeItemsFromUnimportedNamespaces(Document document) - => document.Project.Solution.Options.GetOption(CompletionOptions.Metadata.ShowItemsFromUnimportedNamespaces, document.Project.Language); + public static string? GetProviderName(this CompletionItem completionItem) => completionItem.ProviderName; } } diff --git a/src/Tools/ExternalAccess/OmniSharp/ImplementType/OmniSharpImplementTypeOptions.cs b/src/Tools/ExternalAccess/OmniSharp/ImplementType/OmniSharpImplementTypeOptions.cs index 121e0f7f342ec..6a7c48179da23 100644 --- a/src/Tools/ExternalAccess/OmniSharp/ImplementType/OmniSharpImplementTypeOptions.cs +++ b/src/Tools/ExternalAccess/OmniSharp/ImplementType/OmniSharpImplementTypeOptions.cs @@ -10,16 +10,16 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.ImplementType internal static class OmniSharpImplementTypeOptions { public static OmniSharpImplementTypeInsertionBehavior GetInsertionBehavior(OptionSet options, string language) - => (OmniSharpImplementTypeInsertionBehavior)options.GetOption(ImplementTypeOptions.InsertionBehavior, language); + => (OmniSharpImplementTypeInsertionBehavior)options.GetOption(ImplementTypeOptions.Metadata.InsertionBehavior, language); public static OptionSet SetInsertionBehavior(OptionSet options, string language, OmniSharpImplementTypeInsertionBehavior value) - => options.WithChangedOption(ImplementTypeOptions.InsertionBehavior, language, (ImplementTypeInsertionBehavior)value); + => options.WithChangedOption(ImplementTypeOptions.Metadata.InsertionBehavior, language, (ImplementTypeInsertionBehavior)value); public static OmniSharpImplementTypePropertyGenerationBehavior GetPropertyGenerationBehavior(OptionSet options, string language) - => (OmniSharpImplementTypePropertyGenerationBehavior)options.GetOption(ImplementTypeOptions.PropertyGenerationBehavior, language); + => (OmniSharpImplementTypePropertyGenerationBehavior)options.GetOption(ImplementTypeOptions.Metadata.PropertyGenerationBehavior, language); public static OptionSet SetPropertyGenerationBehavior(OptionSet options, string language, OmniSharpImplementTypePropertyGenerationBehavior value) - => options.WithChangedOption(ImplementTypeOptions.PropertyGenerationBehavior, language, (ImplementTypePropertyGenerationBehavior)value); + => options.WithChangedOption(ImplementTypeOptions.Metadata.PropertyGenerationBehavior, language, (ImplementTypePropertyGenerationBehavior)value); } internal enum OmniSharpImplementTypeInsertionBehavior diff --git a/src/Tools/ExternalAccess/OmniSharp/Rename/OmniSharpRenameOptions.cs b/src/Tools/ExternalAccess/OmniSharp/Rename/OmniSharpRenameOptions.cs new file mode 100644 index 0000000000000..647202d6d5bdc --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Rename/OmniSharpRenameOptions.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Rename; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp +{ + internal readonly record struct OmniSharpRenameOptions( + bool RenameInComments, + bool RenameInStrings, + bool RenameOverloads) + { + internal RenameOptionSet ToRenameOptions() + => new( + RenameOverloads: RenameOverloads, + RenameInStrings: RenameInStrings, + RenameInComments: RenameInComments, + RenameFile: false); + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Rename/OmniSharpRenamer.cs b/src/Tools/ExternalAccess/OmniSharp/Rename/OmniSharpRenamer.cs new file mode 100644 index 0000000000000..d62aced448ddb --- /dev/null +++ b/src/Tools/ExternalAccess/OmniSharp/Rename/OmniSharpRenamer.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Rename; + +namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp +{ + internal static class OmniSharpRenamer + { + public static Task RenameSymbolAsync( + Solution solution, + ISymbol symbol, + string newName, + OmniSharpRenameOptions options, + ImmutableHashSet? nonConflictSymbols, + CancellationToken cancellationToken) + => Renamer.RenameSymbolAsync(solution, symbol, newName, options.ToRenameOptions(), nonConflictSymbols, cancellationToken); + } +} diff --git a/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureOptions.cs b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureOptions.cs index 3a85176e96456..52b0b27612bd5 100644 --- a/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureOptions.cs +++ b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureOptions.cs @@ -3,17 +3,19 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; -using System.Text; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Structure; namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Structure { - internal static class OmniSharpBlockStructureOptions + internal readonly record struct OmniSharpBlockStructureOptions( + bool ShowBlockStructureGuidesForCommentsAndPreprocessorRegions, + bool ShowOutliningForCommentsAndPreprocessorRegions) { - public static readonly PerLanguageOption ShowBlockStructureGuidesForCommentsAndPreprocessorRegions = (PerLanguageOption)BlockStructureOptions.Metadata.ShowBlockStructureGuidesForCommentsAndPreprocessorRegions; - - public static readonly PerLanguageOption ShowOutliningForCommentsAndPreprocessorRegions = (PerLanguageOption)BlockStructureOptions.Metadata.ShowOutliningForCommentsAndPreprocessorRegions; + internal BlockStructureOptions ToBlockStructureOptions() + => BlockStructureOptions.Default with + { + ShowBlockStructureGuidesForCommentsAndPreprocessorRegions = ShowBlockStructureGuidesForCommentsAndPreprocessorRegions, + ShowOutliningForCommentsAndPreprocessorRegions = ShowOutliningForCommentsAndPreprocessorRegions, + }; } } diff --git a/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureService.cs b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureService.cs index 7b56f4a38cf46..8b353d48bbda0 100644 --- a/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureService.cs +++ b/src/Tools/ExternalAccess/OmniSharp/Structure/OmniSharpBlockStructureService.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.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Structure; @@ -13,10 +11,10 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Structure { internal static class OmniSharpBlockStructureService { - public static async Task GetBlockStructureAsync(Document document, CancellationToken cancellationToken) + public static async Task GetBlockStructureAsync(Document document, OmniSharpBlockStructureOptions options, CancellationToken cancellationToken) { var service = document.GetRequiredLanguageService(); - var blockStructure = await service.GetBlockStructureAsync(document, cancellationToken).ConfigureAwait(false); + var blockStructure = await service.GetBlockStructureAsync(document, options.ToBlockStructureOptions(), cancellationToken).ConfigureAwait(false); if (blockStructure != null) { return new OmniSharpBlockStructure(blockStructure.Spans.SelectAsArray(x => new OmniSharpBlockSpan(x.Type, x.IsCollapsible, x.TextSpan, x.HintSpan, x.BannerText, x.AutoCollapse, x.IsDefaultCollapsed))); diff --git a/src/Tools/ExternalAccess/Razor/RazorDocumentServiceProviderWrapper.cs b/src/Tools/ExternalAccess/Razor/RazorDocumentServiceProviderWrapper.cs index 63fc07b6e2c3e..463a02e623599 100644 --- a/src/Tools/ExternalAccess/Razor/RazorDocumentServiceProviderWrapper.cs +++ b/src/Tools/ExternalAccess/Razor/RazorDocumentServiceProviderWrapper.cs @@ -1,27 +1,26 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable enable annotations using System; +using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.Host; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ExternalAccess.Razor { internal sealed class RazorDocumentServiceProviderWrapper : IDocumentServiceProvider, IDocumentOperationService { private readonly IRazorDocumentServiceProvider _innerDocumentServiceProvider; - private readonly object _lock; - private RazorSpanMappingServiceWrapper? _spanMappingService; - private RazorDocumentExcerptServiceWrapper? _excerptService; - private RazorDocumentPropertiesServiceWrapper? _documentPropertiesService; + // The lazily initialized service fields use StrongBox to explicitly allow null as an initialized value. + private StrongBox? _lazySpanMappingService; + private StrongBox? _lazyExcerptService; + private StrongBox? _lazyDocumentPropertiesService; public RazorDocumentServiceProviderWrapper(IRazorDocumentServiceProvider innerDocumentServiceProvider) { _innerDocumentServiceProvider = innerDocumentServiceProvider ?? throw new ArgumentNullException(nameof(innerDocumentServiceProvider)); - - _lock = new object(); } public bool CanApplyChange => _innerDocumentServiceProvider.CanApplyChange; @@ -33,75 +32,44 @@ public RazorDocumentServiceProviderWrapper(IRazorDocumentServiceProvider innerDo var serviceType = typeof(TService); if (serviceType == typeof(ISpanMappingService)) { - if (_spanMappingService == null) - { - lock (_lock) + var spanMappingService = LazyInitialization.EnsureInitialized( + ref _lazySpanMappingService, + static documentServiceProvider => { - if (_spanMappingService == null) - { - var razorMappingService = _innerDocumentServiceProvider.GetService(); - if (razorMappingService != null) - { - _spanMappingService = new RazorSpanMappingServiceWrapper(razorMappingService); - } - else - { - return this as TService; - } - } - } - } + var razorMappingService = documentServiceProvider.GetService(); + return razorMappingService != null ? new RazorSpanMappingServiceWrapper(razorMappingService) : null; + }, + _innerDocumentServiceProvider); - return (TService)(object)_spanMappingService; + return (TService?)spanMappingService; } if (serviceType == typeof(IDocumentExcerptService)) { - if (_excerptService == null) - { - lock (_lock) + var excerptService = LazyInitialization.EnsureInitialized( + ref _lazyExcerptService, + static documentServiceProvider => { - if (_excerptService == null) - { - var excerptService = _innerDocumentServiceProvider.GetService(); - if (excerptService != null) - { - _excerptService = new RazorDocumentExcerptServiceWrapper(excerptService); - } - else - { - return this as TService; - } - } - } - } + var razorExcerptService = documentServiceProvider.GetService(); + return razorExcerptService is not null ? new RazorDocumentExcerptServiceWrapper(razorExcerptService) : null; + }, + _innerDocumentServiceProvider); - return (TService)(object)_excerptService; + return (TService?)excerptService; } if (serviceType == typeof(DocumentPropertiesService)) { - if (_documentPropertiesService == null) - { - lock (_lock) + var documentPropertiesService = LazyInitialization.EnsureInitialized( + ref _lazyDocumentPropertiesService, + static documentServiceProvider => { - if (_documentPropertiesService == null) - { - var documentPropertiesService = _innerDocumentServiceProvider.GetService(); - - if (documentPropertiesService != null) - { - _documentPropertiesService = new RazorDocumentPropertiesServiceWrapper(documentPropertiesService); - } - else - { - return this as TService; - } - } - } - } + var razorDocumentPropertiesService = documentServiceProvider.GetService(); + return razorDocumentPropertiesService is not null ? new RazorDocumentPropertiesServiceWrapper(razorDocumentPropertiesService) : null; + }, + _innerDocumentServiceProvider); - return (TService)(object)_documentPropertiesService; + return (TService?)(object?)documentPropertiesService; } return this as TService; diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs index 039e54099fa28..f71c20f945332 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs +++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs @@ -117,11 +117,11 @@ public AdvancedOptionPageControl(OptionStore optionStore, IComponentModel compon BindToOption(DontPutOutOrRefOnStruct, ExtractMethodOptions.DontPutOutOrRefOnStruct, LanguageNames.CSharp); - BindToOption(with_other_members_of_the_same_kind, ImplementTypeOptions.InsertionBehavior, ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind, LanguageNames.CSharp); - BindToOption(at_the_end, ImplementTypeOptions.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd, LanguageNames.CSharp); + BindToOption(with_other_members_of_the_same_kind, ImplementTypeOptions.Metadata.InsertionBehavior, ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind, LanguageNames.CSharp); + BindToOption(at_the_end, ImplementTypeOptions.Metadata.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd, LanguageNames.CSharp); - BindToOption(prefer_throwing_properties, ImplementTypeOptions.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferThrowingProperties, LanguageNames.CSharp); - BindToOption(prefer_auto_properties, ImplementTypeOptions.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties, LanguageNames.CSharp); + BindToOption(prefer_throwing_properties, ImplementTypeOptions.Metadata.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferThrowingProperties, LanguageNames.CSharp); + BindToOption(prefer_auto_properties, ImplementTypeOptions.Metadata.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties, LanguageNames.CSharp); BindToOption(Report_invalid_placeholders_in_string_dot_format_calls, ValidateFormatStringOption.ReportInvalidPlaceholdersInStringDotFormatCalls, LanguageNames.CSharp); diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioActiveDocumentTracker.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioActiveDocumentTracker.cs index e97deba28cdcd..79f8cb1fcf26b 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioActiveDocumentTracker.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioActiveDocumentTracker.cs @@ -147,10 +147,19 @@ public void TrackNewActiveWindowFrame(IVsWindowFrame frame) _activeFrame = frame; - if (!_visibleFrames.Any(f => f.Frame == frame)) + var existingFrame = _visibleFrames.FirstOrDefault(f => f.Frame == frame); + if (existingFrame == null) { _visibleFrames = _visibleFrames.Add(new FrameListener(this, frame)); } + else if (existingFrame.TextBuffer == null) + { + // If no text buffer is associated with existing frame, remove the existing frame and add the new one. + // Note that we do not need to disconnect the existing frame here. It will get disconnected along with + // the new frame whenever the document is closed or de-activated. + _visibleFrames = _visibleFrames.Remove(existingFrame); + _visibleFrames = _visibleFrames.Add(new FrameListener(this, frame)); + } this.DocumentsChanged?.Invoke(this, EventArgs.Empty); } @@ -210,7 +219,7 @@ private class FrameListener : IVsWindowFrameNotify, IVsWindowFrameNotify2 private readonly VisualStudioActiveDocumentTracker _documentTracker; private readonly uint _frameEventsCookie; - private readonly ITextBuffer? _textBuffer; + internal ITextBuffer? TextBuffer { get; } public FrameListener(VisualStudioActiveDocumentTracker service, IVsWindowFrame frame) { @@ -225,11 +234,11 @@ public FrameListener(VisualStudioActiveDocumentTracker service, IVsWindowFrame f { if (docData is IVsTextBuffer bufferAdapter) { - _textBuffer = _documentTracker._editorAdaptersFactoryService.GetDocumentBuffer(bufferAdapter); + TextBuffer = _documentTracker._editorAdaptersFactoryService.GetDocumentBuffer(bufferAdapter); - if (_textBuffer != null && !_textBuffer.ContentType.IsOfType(ContentTypeNames.RoslynContentType)) + if (TextBuffer != null && !TextBuffer.ContentType.IsOfType(ContentTypeNames.RoslynContentType)) { - _textBuffer.Changed += NonRoslynTextBuffer_Changed; + TextBuffer.Changed += NonRoslynTextBuffer_Changed; } } } @@ -244,12 +253,12 @@ private void NonRoslynTextBuffer_Changed(object sender, TextContentChangedEventA /// public DocumentId? GetDocumentId(Workspace workspace) { - if (_textBuffer == null) + if (TextBuffer == null) { return null; } - var textContainer = _textBuffer.AsTextContainer(); + var textContainer = TextBuffer.AsTextContainer(); return workspace.GetDocumentIdInCurrentContext(textContainer); } @@ -283,9 +292,9 @@ private int Disconnect() _documentTracker.AssertIsForeground(); _documentTracker.RemoveFrame(this); - if (_textBuffer != null) + if (TextBuffer != null) { - _textBuffer.Changed -= NonRoslynTextBuffer_Changed; + TextBuffer.Changed -= NonRoslynTextBuffer_Changed; } if (_frameEventsCookie != VSConstants.VSCOOKIE_NIL) diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb index 26b84d62be62b..9855802da5742 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb +++ b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb @@ -133,11 +133,11 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options BindToOption(DontPutOutOrRefOnStruct, ExtractMethodOptions.DontPutOutOrRefOnStruct, LanguageNames.VisualBasic) ' Implement Interface or Abstract Class - BindToOption(with_other_members_of_the_same_kind, ImplementTypeOptions.InsertionBehavior, ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind, LanguageNames.VisualBasic) - BindToOption(at_the_end, ImplementTypeOptions.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd, LanguageNames.VisualBasic) + BindToOption(with_other_members_of_the_same_kind, ImplementTypeOptions.Metadata.InsertionBehavior, ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind, LanguageNames.VisualBasic) + BindToOption(at_the_end, ImplementTypeOptions.Metadata.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd, LanguageNames.VisualBasic) - BindToOption(prefer_throwing_properties, ImplementTypeOptions.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferThrowingProperties, LanguageNames.VisualBasic) - BindToOption(prefer_auto_properties, ImplementTypeOptions.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties, LanguageNames.VisualBasic) + BindToOption(prefer_throwing_properties, ImplementTypeOptions.Metadata.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferThrowingProperties, LanguageNames.VisualBasic) + BindToOption(prefer_auto_properties, ImplementTypeOptions.Metadata.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties, LanguageNames.VisualBasic) ' Inline hints BindToOption(DisplayAllHintsWhilePressingAltF1, InlineHintsViewOptions.DisplayAllHintsWhilePressingAltF1) diff --git a/src/Workspaces/Core/Portable/Rename/RenameOptions.cs b/src/Workspaces/Core/Portable/Rename/RenameOptions.cs index cac76215e79ac..8d4cfa0164ebe 100644 --- a/src/Workspaces/Core/Portable/Rename/RenameOptions.cs +++ b/src/Workspaces/Core/Portable/Rename/RenameOptions.cs @@ -22,21 +22,12 @@ public static class RenameOptions public static Option PreviewChanges { get; } = new Option(nameof(RenameOptions), nameof(PreviewChanges), defaultValue: false); } - internal struct RenameOptionSet + internal readonly record struct RenameOptionSet( + bool RenameOverloads, + bool RenameInStrings, + bool RenameInComments, + bool RenameFile) { - public readonly bool RenameOverloads; - public readonly bool RenameInStrings; - public readonly bool RenameInComments; - public readonly bool RenameFile; - - public RenameOptionSet(bool renameOverloads, bool renameInStrings, bool renameInComments, bool renameFile) - { - RenameOverloads = renameOverloads; - RenameInStrings = renameInStrings; - RenameInComments = renameInComments; - RenameFile = renameFile; - } - internal static RenameOptionSet From(Solution solution) => From(solution, options: null); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/LazyInitialization.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/LazyInitialization.cs index 11ff82a69439b..556f1985237e8 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/LazyInitialization.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/LazyInitialization.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Threading; namespace Roslyn.Utilities @@ -22,7 +23,7 @@ internal static T InterlockedStore([NotNull] ref T? target, T value) where T /// more than once by multiple threads, but only one of those values will successfully be written to the target. /// The target value. public static T EnsureInitialized([NotNull] ref T? target, Func valueFactory) where T : class - => Volatile.Read(ref target!) ?? InterlockedStore(ref target!, valueFactory()); + => Volatile.Read(ref target!) ?? InterlockedStore(ref target, valueFactory()); /// /// Ensure that the given target value is initialized (not null) in a thread-safe manner. @@ -37,7 +38,41 @@ public static T EnsureInitialized([NotNull] ref T? target, Func valueFacto public static T EnsureInitialized([NotNull] ref T? target, Func valueFactory, U state) where T : class { - return Volatile.Read(ref target!) ?? InterlockedStore(ref target!, valueFactory(state)); + return Volatile.Read(ref target!) ?? InterlockedStore(ref target, valueFactory(state)); + } + + /// + /// Ensure that the given target value is initialized in a thread-safe manner. This overload supports the + /// initialization of value types, and reference type fields where is considered an + /// initialized value. + /// + /// The type of the target value. + /// A target value box to initialize. + /// A factory delegate to create a new instance of the target value. Note that this delegate may be called + /// more than once by multiple threads, but only one of those values will successfully be written to the target. + /// The target value. + public static T? EnsureInitialized([NotNull] ref StrongBox? target, Func valueFactory) + { + var box = Volatile.Read(ref target!) ?? InterlockedStore(ref target, new StrongBox(valueFactory())); + return box.Value; + } + + /// + /// Ensure that the given target value is initialized in a thread-safe manner. This overload supports the + /// initialization of value types, and reference type fields where is considered an + /// initialized value. + /// + /// The type of the target value. + /// A target value box to initialize. + /// The type of the argument passed to the value factory. + /// A factory delegate to create a new instance of the target value. Note that this delegate may be called + /// more than once by multiple threads, but only one of those values will successfully be written to the target. + /// An argument passed to the value factory. + /// The target value. + public static T? EnsureInitialized([NotNull] ref StrongBox? target, Func valueFactory, U state) + { + var box = Volatile.Read(ref target!) ?? InterlockedStore(ref target, new StrongBox(valueFactory(state))); + return box.Value; } } }