diff --git a/docs/features/interceptors.md b/docs/features/interceptors.md index 42557fe9e9964..93404484654fc 100644 --- a/docs/features/interceptors.md +++ b/docs/features/interceptors.md @@ -78,6 +78,7 @@ namespace System.Runtime.CompilerServices `[InterceptsLocation]` attributes included in source are emitted to the resulting assembly, just like other custom attributes. PROTOTYPE(ic): We may want to recognize `file class InterceptsLocationAttribute` as a valid declaration of the attribute, to allow generators to bring the attribute in without conflicting with other generators which may also be bringing the attribute in. See open question in [User opt-in](#user-opt-in). +https://github.com/dotnet/roslyn/issues/67079 is a bug which causes file-local source declarations of well-known attributes to be generally treated as known. When that bug is fixed, we may want to single out one or both of `InterceptableAttribute` and `InterceptsLocationAttribute` as "recognized, even though they are file-local". #### File paths @@ -109,19 +110,45 @@ Interception can only occur for calls to ordinary member methods--not constructo Interceptors cannot have type parameters or be declared in generic types at any level of nesting. -### Signature matching +This limitation prevents interceptors from matching the signature of an interceptable call in cases where the interceptable call uses type parameters which are not in scope at the interceptor declaration. We can consider adjusting the rules to alleviate this limitation if compelling scenarios arise for it in the future. + +```cs +using System.Runtime.CompilerServices; + +class C +{ + [Interceptable] + public static void InterceptableMethod(T1 t) => throw null!; +} + +static class Program +{ + public static void M(T2 t) + { + C.InterceptableMethod(t); + } +} + +static class D +{ + [InterceptsLocation("Program.cs", 13, 11)] + public static void Interceptor1(object s) => throw null!; +} +``` -PROTOTYPE(ic): It is suggested to permit nullability differences and other comparable differences. Perhaps we can revisit the matching requirements of "partial methods" and imitate them here. +### Signature matching When a call is intercepted, the interceptor and interceptable methods must meet the signature matching requirements detailed below: - When an interceptable instance method is compared to a classic extension method, we use the extension method in reduced form for comparison. The extension method parameter with the `this` modifier is compared to the instance method `this` parameter. -- The returns and parameters, including the `this` parameter, must have the same ref kinds and types, except that reference types with oblivious nullability can match either annotated or unannotated reference types. +- The returns and parameters, including the `this` parameter, must have the same ref kinds and types. +- A warning is reported instead of an error if a type difference is found where the types are not distinct to the runtime. For example, `object` and `dynamic`. +- No warning or error is reported for a *safe* nullability difference, such as when the interceptable method accepts a `string` parameter, and the interceptor accepts a `string?` parameter. - Method names and parameter names are not required to match. - Parameter default values are not required to match. When intercepting, default values on the interceptor method are ignored. - `params` modifiers are not required to match. - `scoped` modifiers and `[UnscopedRef]` must be equivalent. - In general, attributes which normally affect the behavior of the call site, such as `[CallerLineNumber]` are ignored on the interceptor of an intercepted call. - - The only exception to this is when the attribute affects "capabilities" of the method in a way that affects safety, such as with `[UnscopedRef]`. In this case, attributes are required to match across interceptable and interceptor methods. + - The only exception to this is when the attribute affects "capabilities" of the method in a way that affects safety, such as with `[UnscopedRef]`. Such attributes are required to match across interceptable and interceptor methods. Arity does not need to match between intercepted and interceptor methods. In other words, it is permitted to intercept a generic method with a non-generic interceptor. @@ -133,7 +160,7 @@ If an `[InterceptsLocation]` attribute is found in the compilation which does no ### Interceptor accessibility -An interceptor must be accessible at the location where interception is occurring. PROTOTYPE(ic): This enforcement is not yet implemented. +An interceptor must be accessible at the location where interception is occurring. An interceptor contained in a file-local type is permitted to intercept a call in another file, even though the interceptor is not normally *visible* at the call site. @@ -157,7 +184,6 @@ During the binding phase, `InterceptsLocationAttribute` usages are decoded and t - intercepted file-path and location - attribute location - attributed method symbol -PROTOTYPE(ic): the exact collection used to collect the attribute usages, and the exact way it is used, are not finalized. The main concern is to ensure we can scale to large numbers of interceptors without issue, and that we can report diagnostics for duplicate interception of the same location in a deterministic way. At this time, diagnostics are reported for the following conditions: - problems specific to the attributed interceptor method itself, for example, that it is not an ordinary method. @@ -172,4 +198,5 @@ During the lowering phase, when a given `BoundCall` is lowered: At this time, diagnostics are reported for the following conditions: - incompatibility between the interceptor and interceptable methods, for example, in their signatures. -- *duplicate* `[InterceptsLocation]`, that is, multiple interceptors which intercept the same call. PROTOTYPE(ic): not yet implemented. +- *duplicate* `[InterceptsLocation]`, that is, multiple interceptors which intercept the same call. +- interceptor is not accessible at the call site. \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 7b8aed9e7bccf..1230658c29293 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -7523,15 +7523,24 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The given line is '{0}' characters long, which is fewer than the provided character number '{1}'. + + Line and character numbers provided to InterceptsLocationAttribute must be positive. + The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'. - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. + + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + + + Signatures of interceptable and interceptor methods do not match. + An interceptable method must be an ordinary member method. @@ -7553,10 +7562,31 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Cannot intercept a call in file with path '{0}' because multiple files in the compilation have this path. + + The indicated call is intercepted multiple times. + + + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + + + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + A constant value of type '{0}' is expected Cannot use primary constructor parameter of type '{0}' inside an instance member + + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + + + Nullability of reference types in type of parameter doesn't match interceptable method. + + + Nullability of reference types in return type doesn't match interceptable method '{0}'. + + + Nullability of reference types in return type doesn't match interceptable method. + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index e1c9c631c6807..58940a92249ae 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -2247,44 +2247,61 @@ internal void AddModuleInitializerMethod(MethodSymbol method) LazyInitializer.EnsureInitialized(ref _moduleInitializerMethods).Add(method); } - private ConcurrentSet<(InterceptsLocationAttributeData, MethodSymbol)>? _interceptions; + // NB: the 'Many' case for these dictionary values means there are duplicates. An error is reported for this after binding. + private ConcurrentDictionary<(string FilePath, int Line, int Character), OneOrMany<(Location AttributeLocation, MethodSymbol Interceptor)>>? _interceptions; - internal void AddInterception(InterceptsLocationAttributeData location, MethodSymbol interceptor) + internal void AddInterception(string filePath, int line, int character, Location attributeLocation, MethodSymbol interceptor) { Debug.Assert(!_declarationDiagnosticsFrozen); - LazyInitializer.EnsureInitialized(ref _interceptions).Add((location, interceptor)); + + var dictionary = LazyInitializer.EnsureInitialized(ref _interceptions); + dictionary.AddOrUpdate((filePath, line, character), + addValueFactory: static (key, newValue) => OneOrMany.Create(newValue), + updateValueFactory: static (key, existingValues, newValue) => + { + // AddInterception can be called when attributes are decoded on a symbol, which can happen for the same symbol concurrently. + // If something else has already added the interceptor denoted by a given `[InterceptsLocation]`, we want to drop it. + // Since the collection is almost always length 1, a simple foreach is adequate for detecting this. + foreach (var (attributeLocation, interceptor) in existingValues) + { + if (attributeLocation == newValue.AttributeLocation && interceptor.Equals(newValue.Interceptor, TypeCompareKind.ConsiderEverything)) + { + return existingValues; + } + } + return existingValues.Add(newValue); + }, + // Explicit tuple element names are needed here so that the names unify when this is an extension method call (netstandard2.0). + factoryArgument: (AttributeLocation: attributeLocation, Interceptor: interceptor)); } - internal (InterceptsLocationAttributeData data, MethodSymbol interceptor)? GetInterceptor(Location? callLocation) + internal (Location AttributeLocation, MethodSymbol Interceptor)? TryGetInterceptor(Location? callLocation, BindingDiagnosticBag diagnostics) { if (_interceptions is null || callLocation is null) { return null; } - var sourceTree = callLocation.SourceTree; - Debug.Assert(sourceTree is not null); var callLineColumn = callLocation.GetLineSpan().Span.Start; - foreach (var (interceptsLocation, interceptor) in _interceptions) + Debug.Assert(callLocation.SourceTree is not null); + var key = (callLocation.SourceTree.FilePath, callLineColumn.Line, callLineColumn.Character); + + if (_interceptions.TryGetValue(key, out var interceptionsAtAGivenLocation)) { - if (interceptsLocation.FilePath == sourceTree.FilePath - && interceptsLocation.Line == callLineColumn.Line - && interceptsLocation.Character == callLineColumn.Character) + if (interceptionsAtAGivenLocation is [var oneInterception]) { - return (interceptsLocation, interceptor); + return oneInterception; } + + // We don't normally reach this branch in batch compilation, because we would have already reported an error after the declaration phase. + // One scenario where we may reach this is when validating used assemblies, which performs lowering of method bodies even if declaration errors would be reported. + // See 'CSharpCompilation.GetCompleteSetOfUsedAssemblies'. + diagnostics.Add(ErrorCode.ERR_ModuleEmitFailure, callLocation, this.SourceModule.Name, new LocalizableResourceString(nameof(CSharpResources.ERR_DuplicateInterceptor), CodeAnalysisResources.ResourceManager, typeof(CodeAnalysisResources))); } return null; } - private void BuildInterceptionsMap() - { - // PROTOTYPE(ic): build a map where we can quickly lookup with a location and get a symbol. - // At this time, should report any duplicate interception diagnostics. - // NB: the attribute which appears lexically first wins a tie. Subsequent attributes referring to same location result in errors. - } - #endregion #region Binding @@ -3244,6 +3261,8 @@ internal override bool CompileMethods( bool hasDeclarationErrors = !FilterAndAppendDiagnostics(diagnostics, GetDiagnostics(CompilationStage.Declare, true, cancellationToken), excludeDiagnostics, cancellationToken); excludeDiagnostics?.Free(); + hasDeclarationErrors |= CheckDuplicateInterceptions(diagnostics); + // TODO (tomat): NoPIA: // EmbeddedSymbolManager.MarkAllDeferredSymbolsAsReferenced(this) @@ -3275,8 +3294,6 @@ internal override bool CompileMethods( return false; } - BuildInterceptionsMap(); - // Perform initial bind of method bodies in spite of earlier errors. This is the same // behavior as when calling GetDiagnostics() @@ -3379,12 +3396,40 @@ public override void VisitNamedType(NamedTypeSymbol symbol) } } + /// if file types are present in files with duplicate file paths. Otherwise, . private bool CheckDuplicateFilePaths(DiagnosticBag diagnostics) { var visitor = new DuplicateFilePathsVisitor(diagnostics); return visitor.CheckDuplicateFilePathsAndFree(SyntaxTrees, GlobalNamespace); } + /// if duplicate interceptors are present in the compilation. Otherwise, . + private bool CheckDuplicateInterceptions(DiagnosticBag diagnostics) + { + if (_interceptions is null) + { + return false; + } + + bool anyDuplicates = false; + foreach ((_, OneOrMany<(Location, MethodSymbol)> interceptionsOfAGivenLocation) in _interceptions) + { + Debug.Assert(interceptionsOfAGivenLocation.Count != 0); + if (interceptionsOfAGivenLocation.Count == 1) + { + continue; + } + + anyDuplicates = true; + foreach (var (attributeLocation, _) in interceptionsOfAGivenLocation) + { + diagnostics.Add(ErrorCode.ERR_DuplicateInterceptor, attributeLocation); + } + } + + return anyDuplicates; + } + private void GenerateModuleInitializer(PEModuleBuilder moduleBeingBuilt, DiagnosticBag methodBodyDiagnosticBag) { Debug.Assert(_declarationDiagnosticsFrozen); diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index 4139b33f4e855..5f8259359fa82 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -120,6 +120,11 @@ public static void CompileMethodBodies( Debug.Assert(diagnostics != null); Debug.Assert(diagnostics.DiagnosticBag != null); + // PROTOTYPE(ic): + // - Move check for duplicate interceptions in here. + // - Change lowering to throw on duplicates. + // - Test no duplicate error given when emitting a ref assembly. + if (compilation.PreviousSubmission != null) { // In case there is a previous submission, we should ensure diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 8e416735cd8be..dd15a76e93da6 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2209,6 +2209,14 @@ internal enum ErrorCode ERR_InterceptorFilePathCannotBeNull = 27013, ERR_InterceptorNameNotInvoked = 27014, ERR_InterceptorNonUniquePath = 27015, + ERR_DuplicateInterceptor = 27016, + WRN_InterceptorSignatureMismatch = 27017, + ERR_InterceptorNotAccessible = 27018, + ERR_InterceptorScopedMismatch = 27019, + ERR_InterceptorLineCharacterMustBePositive = 27020, + WRN_NullabilityMismatchInReturnTypeOnInterceptor = 27021, + WRN_NullabilityMismatchInParameterTypeOnInterceptor = 27022, + #endregion // Note: you will need to do the following after adding warnings: diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs index eb58ca5815744..c78d60a2a6d3b 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -80,6 +80,8 @@ static ErrorFacts() nullableWarnings.Add(GetId(ErrorCode.WRN_ParameterDisallowsNull)); nullableWarnings.Add(GetId(ErrorCode.WRN_ParameterNotNullIfNotNull)); nullableWarnings.Add(GetId(ErrorCode.WRN_ReturnNotNullIfNotNull)); + nullableWarnings.Add(GetId(ErrorCode.WRN_NullabilityMismatchInReturnTypeOnInterceptor)); + nullableWarnings.Add(GetId(ErrorCode.WRN_NullabilityMismatchInParameterTypeOnInterceptor)); NullableWarnings = nullableWarnings.ToImmutable(); } @@ -531,6 +533,9 @@ internal static int GetWarningLevel(ErrorCode code) case ErrorCode.WRN_CapturedPrimaryConstructorParameterPassedToBase: case ErrorCode.WRN_UnreadPrimaryConstructorParameter: case ErrorCode.WRN_CallNotInterceptable: + case ErrorCode.WRN_InterceptorSignatureMismatch: + case ErrorCode.WRN_NullabilityMismatchInReturnTypeOnInterceptor: + case ErrorCode.WRN_NullabilityMismatchInParameterTypeOnInterceptor: return 1; default: return 0; @@ -584,6 +589,12 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code) case ErrorCode.ERR_InterceptorSignatureMismatch: case ErrorCode.ERR_InterceptorMustHaveMatchingThisParameter: case ErrorCode.ERR_InterceptorMustNotHaveThisParameter: + case ErrorCode.ERR_DuplicateInterceptor: + case ErrorCode.WRN_InterceptorSignatureMismatch: + case ErrorCode.ERR_InterceptorNotAccessible: + case ErrorCode.ERR_InterceptorScopedMismatch: + case ErrorCode.WRN_NullabilityMismatchInReturnTypeOnInterceptor: + case ErrorCode.WRN_NullabilityMismatchInParameterTypeOnInterceptor: // Update src\EditorFeatures\CSharp\LanguageServer\CSharpLspBuildOnlyDiagnostics.cs // whenever new values are added here. return true; @@ -2326,6 +2337,7 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code) case ErrorCode.ERR_InterceptorFilePathCannotBeNull: case ErrorCode.ERR_InterceptorNameNotInvoked: case ErrorCode.ERR_InterceptorNonUniquePath: + case ErrorCode.ERR_InterceptorLineCharacterMustBePositive: case ErrorCode.ERR_ConstantValueOfTypeExpected: case ErrorCode.ERR_UnsupportedPrimaryConstructorParameterCapturingRefAny: return false; diff --git a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs index 3f0b27ee459b2..b726052c876be 100644 --- a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs @@ -316,6 +316,9 @@ public static bool IsWarning(ErrorCode code) case ErrorCode.WRN_UnreadPrimaryConstructorParameter: case ErrorCode.WRN_AddressOfInAsync: case ErrorCode.WRN_CallNotInterceptable: + case ErrorCode.WRN_InterceptorSignatureMismatch: + case ErrorCode.WRN_NullabilityMismatchInReturnTypeOnInterceptor: + case ErrorCode.WRN_NullabilityMismatchInParameterTypeOnInterceptor: return true; default: return false; diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs index 092b989bdbf37..aa8f81bb2bc6b 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs @@ -138,45 +138,72 @@ private void InterceptCallAndAdjustArguments( ref BoundExpression? receiverOpt, ref ImmutableArray arguments, ref ImmutableArray argumentRefKindsOpt, - ref bool invokedAsExtensionMethod, + bool invokedAsExtensionMethod, Location? interceptableLocation) { // PROTOTYPE(ic): // Add assertions for the possible shapes of calls which could come through this method. // When the BoundCall shape changes in the future, force developer to decide what to do here. - // PROTOTYPE: perhaps a 'TryGet' pattern is more suitable here. - if (this._compilation.GetInterceptor(interceptableLocation) is not var (interceptsLocationAttributeData, interceptor)) + if (this._compilation.TryGetInterceptor(interceptableLocation, _diagnostics) is not var (attributeLocation, interceptor)) { // The call was not intercepted. return; } Debug.Assert(interceptableLocation != null); + Debug.Assert(interceptor.Arity == 0); + if (!method.IsInterceptable) { - // PROTOTYPE(ic): it was speculated that we could avoid work if we know the current method is not interceptable. - // i.e. use this as an early out before even calling Compilation.GetInterceptor. - // But by calling 'GetInterceptor' before this, we don't really avoid that work. Is that fine? - // PROTOTYPE(ic): eventually we probably want this to be an error but for now it's convenient to just warn - // so we can experiment with intercepting APIs that haven't yet been marked. - this._diagnostics.Add(ErrorCode.WRN_CallNotInterceptable, interceptsLocationAttributeData.AttributeLocation, method); + // PROTOTYPE(ic): Eventually we may want this to be an error. + // For now it's convenient to just warn so we can experiment with intercepting APIs that haven't yet been marked. + this._diagnostics.Add(ErrorCode.WRN_CallNotInterceptable, attributeLocation, method); } - Debug.Assert(interceptor.Arity == 0); + var containingMethod = this._factory.CurrentFunction; + Debug.Assert(containingMethod is not null); + + var useSiteInfo = this.GetNewCompoundUseSiteInfo(); + var isAccessible = AccessCheck.IsSymbolAccessible(interceptor, containingMethod.ContainingType, ref useSiteInfo); + this._diagnostics.Add(attributeLocation, useSiteInfo); + if (!isAccessible) + { + this._diagnostics.Add(ErrorCode.ERR_InterceptorNotAccessible, attributeLocation, interceptor, containingMethod); + return; + } // When the original call is to an instance method, and the interceptor is an extension method, // we need to take special care to intercept with the extension method as though it is being called in reduced form. Debug.Assert(receiverOpt is not BoundTypeExpression || method.IsStatic); var needToReduce = receiverOpt is not (null or BoundTypeExpression) && interceptor.IsExtensionMethod; var symbolForCompare = needToReduce ? ReducedExtensionMethodSymbol.Create(interceptor, receiverOpt!.Type, _compilation) : interceptor; - if (!MemberSignatureComparer.InterceptorsComparer.Equals(method, symbolForCompare)) // PROTOTYPE(ic): also checked for 'scoped'/'[UnscopedRef]' differences + + if (!MemberSignatureComparer.InterceptorsComparer.Equals(method, symbolForCompare)) { - this._diagnostics.Add(ErrorCode.ERR_InterceptorSignatureMismatch, interceptableLocation, method, interceptor); + this._diagnostics.Add(ErrorCode.ERR_InterceptorSignatureMismatch, attributeLocation, method, interceptor); return; } - // PROTOTYPE(ic): should we also reduce both methods if 'method' is being called as an extension method? + _ = SourceMemberContainerTypeSymbol.CheckValidNullableMethodOverride( + _compilation, + method, + symbolForCompare, + _diagnostics, + static (diagnostics, method, interceptor, topLevel, attributeLocation) => + { + diagnostics.Add(ErrorCode.WRN_NullabilityMismatchInReturnTypeOnInterceptor, attributeLocation, method); + }, + static (diagnostics, method, interceptor, implementingParameter, blameAttributes, attributeLocation) => + { + diagnostics.Add(ErrorCode.WRN_NullabilityMismatchInParameterTypeOnInterceptor, attributeLocation, new FormattedSymbol(implementingParameter, SymbolDisplayFormat.ShortFormat), method); + }, + extraArgument: attributeLocation); + + if (!MemberSignatureComparer.InterceptorsStrictComparer.Equals(method, symbolForCompare)) + { + this._diagnostics.Add(ErrorCode.WRN_InterceptorSignatureMismatch, attributeLocation, method, interceptor); + } method.TryGetThisParameter(out var methodThisParameter); symbolForCompare.TryGetThisParameter(out var interceptorThisParameterForCompare); @@ -185,15 +212,40 @@ private void InterceptCallAndAdjustArguments( case (not null, null): case (not null, not null) when !methodThisParameter.Type.Equals(interceptorThisParameterForCompare.Type, TypeCompareKind.ObliviousNullableModifierMatchesAny) || methodThisParameter.RefKind != interceptorThisParameterForCompare.RefKind: // PROTOTYPE(ic): and ref custom modifiers are equal? - this._diagnostics.Add(ErrorCode.ERR_InterceptorMustHaveMatchingThisParameter, interceptsLocationAttributeData.AttributeLocation, methodThisParameter, method); - break; + this._diagnostics.Add(ErrorCode.ERR_InterceptorMustHaveMatchingThisParameter, attributeLocation, methodThisParameter, method); + return; case (null, not null): - this._diagnostics.Add(ErrorCode.ERR_InterceptorMustNotHaveThisParameter, interceptsLocationAttributeData.AttributeLocation, method); - break; + this._diagnostics.Add(ErrorCode.ERR_InterceptorMustNotHaveThisParameter, attributeLocation, method); + return; default: break; } + if (invokedAsExtensionMethod && interceptor.IsStatic && !interceptor.IsExtensionMethod) + { + // Special case when intercepting an extension method call in reduced form with a non-extension. + this._diagnostics.Add(ErrorCode.ERR_InterceptorMustHaveMatchingThisParameter, attributeLocation, method.Parameters[0], method); + // PROTOYPE(ic): use a symbol display format which includes the 'this' modifier? + //this._diagnostics.Add(ErrorCode.ERR_InterceptorMustHaveMatchingThisParameter, attributeLocation, new FormattedSymbol(method.Parameters[0], SymbolDisplayFormat.CSharpErrorMessageFormat), method); + return; + } + + if (SourceMemberContainerTypeSymbol.CheckValidScopedOverride( + method, + symbolForCompare, + this._diagnostics, + static (diagnostics, method, symbolForCompare, implementingParameter, blameAttributes, attributeLocation) => + { + diagnostics.Add(ErrorCode.ERR_InterceptorScopedMismatch, attributeLocation, method, symbolForCompare); + }, + extraArgument: attributeLocation, + allowVariance: true, + // Since we've already reduced 'symbolForCompare', we compare as though it is not an extension. + invokedAsExtensionMethod: false)) + { + return; + } + if (needToReduce) { Debug.Assert(methodThisParameter is not null); @@ -211,9 +263,6 @@ private void InterceptCallAndAdjustArguments( { argumentRefKindsOpt = argumentRefKindsOpt.Insert(0, thisRefKind); } - - // PROTOTYPE(ic): search for a scenario where propagating this value matters - invokedAsExtensionMethod = true; } method = interceptor; @@ -248,16 +297,14 @@ public override BoundNode VisitCall(BoundCall node) node.Syntax, rewrittenArguments, method, - node.Expanded, // PROTOTYPE(ic): params differences shouldn't matter--maybe even make it an error--but we need to test + node.Expanded, argsToParamsOpt, ref argRefKindsOpt, ref temps, invokedAsExtensionMethod); - InterceptCallAndAdjustArguments(ref method, ref rewrittenReceiver, ref rewrittenArguments, ref argRefKindsOpt, ref invokedAsExtensionMethod, node.InterceptableLocation); - - // PROTOTYPE(ic): intercept with object.ReferenceEquals? - var rewrittenCall = MakeCall(node, node.Syntax, rewrittenReceiver, method, rewrittenArguments, argRefKindsOpt, invokedAsExtensionMethod, node.ResultKind, node.Type, temps.ToImmutableAndFree()); + InterceptCallAndAdjustArguments(ref method, ref rewrittenReceiver, ref rewrittenArguments, ref argRefKindsOpt, invokedAsExtensionMethod, node.InterceptableLocation); + var rewrittenCall = MakeCall(node, node.Syntax, rewrittenReceiver, method, rewrittenArguments, argRefKindsOpt, node.ResultKind, node.Type, temps.ToImmutableAndFree()); if (Instrument) { @@ -291,7 +338,7 @@ private BoundExpression MakeArgumentsAndCall( ref temps, invokedAsExtensionMethod); - return MakeCall(nodeOpt, syntax, rewrittenReceiver, method, arguments, argumentRefKindsOpt, invokedAsExtensionMethod, resultKind, type, temps.ToImmutableAndFree()); + return MakeCall(nodeOpt, syntax, rewrittenReceiver, method, arguments, argumentRefKindsOpt, resultKind, type, temps.ToImmutableAndFree()); } private BoundExpression MakeCall( @@ -301,7 +348,6 @@ private BoundExpression MakeCall( MethodSymbol method, ImmutableArray rewrittenArguments, ImmutableArray argumentRefKinds, - bool invokedAsExtensionMethod, LookupResultKind resultKind, TypeSymbol type, ImmutableArray temps) @@ -337,11 +383,11 @@ private BoundExpression MakeCall( rewrittenReceiver, method, rewrittenArguments, - default(ImmutableArray), + argumentNamesOpt: default(ImmutableArray), argumentRefKinds, isDelegateCall: false, expanded: false, - invokedAsExtensionMethod: invokedAsExtensionMethod, + invokedAsExtensionMethod: false, argsToParamsOpt: default(ImmutableArray), defaultArguments: default(BitVector), resultKind: resultKind, @@ -353,13 +399,13 @@ private BoundExpression MakeCall( rewrittenReceiver, method, rewrittenArguments, - default(ImmutableArray), + argumentNamesOpt: default(ImmutableArray), argumentRefKinds, node.IsDelegateCall, - false, - node.InvokedAsExtensionMethod, - default(ImmutableArray), - default(BitVector), + expanded: false, + invokedAsExtensionMethod: false, + argsToParamsOpt: default(ImmutableArray), + defaultArguments: default(BitVector), node.ResultKind, node.Type); } @@ -386,7 +432,6 @@ private BoundExpression MakeCall(SyntaxNode syntax, BoundExpression? rewrittenRe method: method, rewrittenArguments: rewrittenArguments, argumentRefKinds: default(ImmutableArray), - invokedAsExtensionMethod: false, resultKind: LookupResultKind.Viable, type: type, temps: default); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs index 121bc92182a8a..afe7465031b9a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs @@ -199,10 +199,10 @@ private BoundExpression MakeDynamicCollectionInitializer(BoundExpression rewritt { Debug.Assert(temps.Count == 0); temps.Free(); - return initializer.Update(addMethod, rewrittenArguments, rewrittenReceiver, expanded: false, argsToParamsOpt: default, defaultArguments: default, initializer.InvokedAsExtensionMethod, initializer.ResultKind, rewrittenType); + return initializer.Update(addMethod, rewrittenArguments, rewrittenReceiver, expanded: false, argsToParamsOpt: default, defaultArguments: default, invokedAsExtensionMethod: false, initializer.ResultKind, rewrittenType); } - return MakeCall(null, syntax, rewrittenReceiver, addMethod, rewrittenArguments, argumentRefKindsOpt, initializer.InvokedAsExtensionMethod, initializer.ResultKind, addMethod.ReturnType, temps.ToImmutableAndFree()); + return MakeCall(null, syntax, rewrittenReceiver, addMethod, rewrittenArguments, argumentRefKindsOpt, initializer.ResultKind, addMethod.ReturnType, temps.ToImmutableAndFree()); } private BoundExpression VisitObjectInitializerMember(BoundObjectInitializerMember node, ref BoundExpression rewrittenReceiver, ArrayBuilder sideEffects, ref ArrayBuilder? temps) diff --git a/src/Compilers/CSharp/Portable/Symbols/Attributes/InterceptsLocationAttributeData.cs b/src/Compilers/CSharp/Portable/Symbols/Attributes/InterceptsLocationAttributeData.cs deleted file mode 100644 index 746dbdea4d770..0000000000000 --- a/src/Compilers/CSharp/Portable/Symbols/Attributes/InterceptsLocationAttributeData.cs +++ /dev/null @@ -1,14 +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. - -namespace Microsoft.CodeAnalysis.CSharp -{ - /// - /// Information decoded from InterceptsLocationAttribute. - /// - /// The 0-indexed line number. - /// The 0-indexed character number. - // PROTOTYPE(ic): move away from records - internal sealed record InterceptsLocationAttributeData(string FilePath, int Line, int Character, Location AttributeLocation); -} diff --git a/src/Compilers/CSharp/Portable/Symbols/MemberSignatureComparer.cs b/src/Compilers/CSharp/Portable/Symbols/MemberSignatureComparer.cs index ece99c14d634c..41236e3205fdb 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MemberSignatureComparer.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MemberSignatureComparer.cs @@ -129,7 +129,20 @@ internal sealed class MemberSignatureComparer : IEqualityComparer typeComparison: TypeCompareKind.AllIgnoreOptions); /// - /// This instance is used to determine if an interceptor can be applied to an interceptable method. + /// This instance is used to determine if a partial method implementation matches the definition, + /// including differences ignored by the runtime. + /// + public static readonly MemberSignatureComparer PartialMethodsStrictComparer = new MemberSignatureComparer( + considerName: true, + considerExplicitlyImplementedInterfaces: true, + considerReturnType: true, + considerTypeConstraints: false, + considerCallingConvention: false, + considerRefKindDifferences: true, + typeComparison: TypeCompareKind.ObliviousNullableModifierMatchesAny); + + /// + /// Determines if an interceptor has a compatible signature with an interceptable method. /// NB: when a classic extension method is intercepting an instance method call, a normalization to 'ReducedExtensionMethodSymbol' must be performed first. /// public static readonly MemberSignatureComparer InterceptorsComparer = new MemberSignatureComparer( @@ -140,20 +153,22 @@ internal sealed class MemberSignatureComparer : IEqualityComparer considerCallingConvention: false, considerRefKindDifferences: true, considerArity: false, - typeComparison: TypeCompareKind.ObliviousNullableModifierMatchesAny); + typeComparison: TypeCompareKind.AllIgnoreOptions); /// - /// This instance is used to determine if a partial method implementation matches the definition, - /// including differences ignored by the runtime. + /// Determines if an interceptor has a compatible signature with an interceptable method. + /// If methods are considered equal by , but not equal by this comparer, a warning is reported. + /// NB: when a classic extension method is intercepting an instance method call, a normalization to 'ReducedExtensionMethodSymbol' must be performed first. /// - public static readonly MemberSignatureComparer PartialMethodsStrictComparer = new MemberSignatureComparer( - considerName: true, - considerExplicitlyImplementedInterfaces: true, + public static readonly MemberSignatureComparer InterceptorsStrictComparer = new MemberSignatureComparer( + considerName: false, + considerExplicitlyImplementedInterfaces: false, considerReturnType: true, considerTypeConstraints: false, considerCallingConvention: false, considerRefKindDifferences: true, - typeComparison: TypeCompareKind.ObliviousNullableModifierMatchesAny); + considerArity: false, + typeComparison: TypeCompareKind.AllNullableIgnoreOptions | TypeCompareKind.IgnoreTupleNames); /// /// This instance is used to check whether one member overrides another, according to the C# definition. diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs index 56f8c8989d4e7..e1fee3b2c46a9 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs @@ -600,7 +600,6 @@ private void DecodeWellKnownAttributeAppliedToMethod(ref DecodeWellKnownAttribut { if (MethodKind != MethodKind.Ordinary) { - // PROTOTYPE(ic): consider relaxing this in future. diagnostics.Add(ErrorCode.ERR_InterceptableMethodMustBeOrdinary, arguments.AttributeSyntaxOpt.Location); } arguments.GetOrCreateData().HasInterceptableAttribute = true; @@ -951,7 +950,8 @@ private void DecodeInterceptsLocationAttribute(DecodeWellKnownAttributeArguments { Debug.Assert(arguments.AttributeSyntaxOpt is object); Debug.Assert(!arguments.Attribute.HasErrors); - var attributeArguments = arguments.Attribute.CommonConstructorArguments; + var attributeData = arguments.Attribute; + var attributeArguments = attributeData.CommonConstructorArguments; if (attributeArguments is not [ { Type.SpecialType: SpecialType.System_String }, { Kind: not TypedConstantKind.Array, Value: int lineNumberOneBased }, @@ -962,43 +962,44 @@ private void DecodeInterceptsLocationAttribute(DecodeWellKnownAttributeArguments } var diagnostics = (BindingDiagnosticBag)arguments.Diagnostics; - var attributeLocation = arguments.AttributeSyntaxOpt.Location; + var attributeSyntax = arguments.AttributeSyntaxOpt; + var attributeLocation = attributeSyntax.Location; + const int filePathParameterIndex = 0; + const int lineNumberParameterIndex = 1; + const int characterNumberParameterIndex = 2; var filePath = (string?)attributeArguments[0].Value; if (filePath is null) { - diagnostics.Add(ErrorCode.ERR_InterceptorFilePathCannotBeNull, attributeLocation); + diagnostics.Add(ErrorCode.ERR_InterceptorFilePathCannotBeNull, attributeData.GetAttributeArgumentSyntaxLocation(filePathParameterIndex, attributeSyntax)); return; } if (Arity != 0 || ContainingType.IsGenericType) { - // PROTOTYPE(ic): for now, let's disallow type arguments on the method or containing types. - // eventually, we could consider doing a type argument inference, seeing if the interceptor's type constraints are met, etc... - // but let's not bother unless somebody actually needs it. diagnostics.Add(ErrorCode.ERR_InterceptorCannotBeGeneric, attributeLocation, this); return; } if (MethodKind != MethodKind.Ordinary) { - // PROTOTYPE(ic): consider relaxing this in future. diagnostics.Add(ErrorCode.ERR_InterceptorMethodMustBeOrdinary, attributeLocation); return; } var syntaxTrees = DeclaringCompilation.SyntaxTrees; + // PROTOTYPE(ic): consider avoiding an array allocation here, on the assumption that 1 matching tree is the success (common) case, 0 is the most common error case, and 2 or more is much more rare. var matchingTrees = syntaxTrees.WhereAsArray(static (tree, filePath) => tree.FilePath == filePath, filePath); if (matchingTrees is []) { var suffixMatch = syntaxTrees.FirstOrDefault(static (tree, filePath) => tree.FilePath.EndsWith(filePath), filePath); if (suffixMatch != null) { - diagnostics.Add(ErrorCode.ERR_InterceptorPathNotInCompilationWithCandidate, attributeLocation, filePath, suffixMatch.FilePath); + diagnostics.Add(ErrorCode.ERR_InterceptorPathNotInCompilationWithCandidate, attributeData.GetAttributeArgumentSyntaxLocation(filePathParameterIndex, attributeSyntax), filePath, suffixMatch.FilePath); } else { - diagnostics.Add(ErrorCode.ERR_InterceptorPathNotInCompilation, attributeLocation, filePath); + diagnostics.Add(ErrorCode.ERR_InterceptorPathNotInCompilation, attributeData.GetAttributeArgumentSyntaxLocation(filePathParameterIndex, attributeSyntax), filePath); } return; @@ -1006,20 +1007,27 @@ private void DecodeInterceptsLocationAttribute(DecodeWellKnownAttributeArguments if (matchingTrees is not [var matchingTree]) { - diagnostics.Add(ErrorCode.ERR_InterceptorNonUniquePath, attributeLocation, filePath); + diagnostics.Add(ErrorCode.ERR_InterceptorNonUniquePath, attributeData.GetAttributeArgumentSyntaxLocation(filePathParameterIndex, attributeSyntax), filePath); return; } // Internally, line and character numbers are 0-indexed, but when they appear in code or diagnostic messages, they are 1-indexed. - // PROTOTYPE(ic): test with zero or negative display line/character numbers. int lineNumberZeroBased = lineNumberOneBased - 1; int characterNumberZeroBased = characterNumberOneBased - 1; + if (lineNumberZeroBased < 0 || characterNumberZeroBased < 0) + { + var location = attributeData.GetAttributeArgumentSyntaxLocation(lineNumberZeroBased < 0 ? lineNumberParameterIndex : characterNumberParameterIndex, attributeSyntax); + diagnostics.Add(ErrorCode.ERR_InterceptorLineCharacterMustBePositive, location); + return; + } + var referencedLines = matchingTree.GetText().Lines; var referencedLineCount = referencedLines.Count; + if (lineNumberZeroBased >= referencedLineCount) { - diagnostics.Add(ErrorCode.ERR_InterceptorLineOutOfRange, attributeLocation, referencedLineCount, lineNumberOneBased); + diagnostics.Add(ErrorCode.ERR_InterceptorLineOutOfRange, attributeData.GetAttributeArgumentSyntaxLocation(lineNumberParameterIndex, attributeSyntax), referencedLineCount, lineNumberOneBased); return; } @@ -1027,7 +1035,7 @@ private void DecodeInterceptsLocationAttribute(DecodeWellKnownAttributeArguments var lineLength = line.End - line.Start; if (characterNumberZeroBased >= lineLength) { - diagnostics.Add(ErrorCode.ERR_InterceptorCharacterOutOfRange, attributeLocation, lineLength, characterNumberOneBased); + diagnostics.Add(ErrorCode.ERR_InterceptorCharacterOutOfRange, attributeData.GetAttributeArgumentSyntaxLocation(characterNumberParameterIndex, attributeSyntax), lineLength, characterNumberOneBased); return; } @@ -1051,19 +1059,15 @@ private void DecodeInterceptsLocationAttribute(DecodeWellKnownAttributeArguments return; } - // Did they actually refer to the start of the token, not the middle, or in leading trivia? - var tokenPositionDifference = referencedPosition - referencedToken.Span.Start; - if (tokenPositionDifference != 0) + // Did they actually refer to the start of the token, not the middle, or in trivia? + if (referencedPosition != referencedToken.Span.Start) { - // PROTOTYPE(ic): when a token's leading trivia spans multiple lines, this doesn't suggest a valid position. - diagnostics.Add(ErrorCode.ERR_InterceptorMustReferToStartOfTokenPosition, attributeLocation, referencedToken.Text, characterNumberOneBased - tokenPositionDifference); + var linePositionZeroBased = referencedToken.GetLocation().GetLineSpan().StartLinePosition; + diagnostics.Add(ErrorCode.ERR_InterceptorMustReferToStartOfTokenPosition, attributeLocation, referencedToken.Text, linePositionZeroBased.Line + 1, linePositionZeroBased.Character + 1); return; } - // PROTOTYPE(ic): The attribute should probably be expected to contain "display locations" (1-indexed) a la Diagnostic.ToString(). - // But to do this, we would want to expose helper API for source generators, to produce "display locations" to put in the attribute. - // We would normalize to 0-indexed in this step. all our location-oriented complaints are made here, so we shouldn't need to convert back to "display location" after that point. - DeclaringCompilation.AddInterception(new InterceptsLocationAttributeData(filePath, lineNumberZeroBased, characterNumberZeroBased, attributeLocation), this); + DeclaringCompilation.AddInterception(filePath, lineNumberZeroBased, characterNumberZeroBased, attributeLocation, this); } private void DecodeUnmanagedCallersOnlyAttribute(ref DecodeWellKnownAttributeArguments arguments) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 82b4adedf0545..a5a18ed706044 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -487,6 +487,11 @@ Položka {0} je explicitně implementována více než jednou. + + The indicated call is intercepted multiple times. + The indicated call is intercepted multiple times. + + '{0}' is already listed in the interface list on type '{2}' as '{1}'. {0} je již uvedeno v seznamu rozhraní u typu {2} jako {1}. @@ -862,6 +867,11 @@ Interceptor cannot have a 'null' file path. + + Line and character numbers provided to InterceptsLocationAttribute must be positive. + Line and character numbers provided to InterceptsLocationAttribute must be positive. + + The given file has '{0}' lines, which is fewer than the provided line number '{1}'. The given file has '{0}' lines, which is fewer than the provided line number '{1}'. @@ -883,8 +893,8 @@ - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? @@ -897,6 +907,11 @@ Cannot intercept a call in file with path '{0}' because multiple files in the compilation have this path. + + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + + Cannot intercept: compilation does not contain a file with path '{0}'. Cannot intercept: compilation does not contain a file with path '{0}'. @@ -912,6 +927,11 @@ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'. + + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + + Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. @@ -2307,6 +2327,16 @@ Použití proměnné v tomto kontextu může vystavit odkazované proměnné mimo rozsah jejich oboru. + + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + + + + Signatures of interceptable and interceptor methods do not match. + Signatures of interceptable and interceptor methods do not match. + + InterpolatedStringHandlerArgument has no effect when applied to lambda parameters and will be ignored at the call site. Argument InterpolatedStringHandlerArgument nemá při použití u parametrů lambda žádný účinek a bude se ignorovat v lokalitě volání. @@ -2357,6 +2387,26 @@ Větve podmíněného operátoru odkazu odkazují na proměnné s nekompatibilními obory deklarací + + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + + + + Nullability of reference types in type of parameter doesn't match interceptable method. + Nullability of reference types in type of parameter doesn't match interceptable method. + + + + Nullability of reference types in return type doesn't match interceptable method '{0}'. + Nullability of reference types in return type doesn't match interceptable method '{0}'. + + + + Nullability of reference types in return type doesn't match interceptable method. + Nullability of reference types in return type doesn't match interceptable method. + + Required member '{0}' should not be attributed with 'ObsoleteAttribute' unless the containing type is obsolete or all constructors are obsolete. Požadovaný člen {0} by neměl být přiřazen atributem ObsoleteAttribute, pokud není obsahující typ zastaralý nebo jsou zastaralé všechny konstruktory. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index be9eec4580f05..9245d5b46c06d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -487,6 +487,11 @@ "{0}" ist mehrfach explizit implementiert. + + The indicated call is intercepted multiple times. + The indicated call is intercepted multiple times. + + '{0}' is already listed in the interface list on type '{2}' as '{1}'. "{0}" wird in der Schnittstellenliste bereits für den Typ "{2}" als "{1}" aufgeführt. @@ -862,6 +867,11 @@ Interceptor cannot have a 'null' file path. + + Line and character numbers provided to InterceptsLocationAttribute must be positive. + Line and character numbers provided to InterceptsLocationAttribute must be positive. + + The given file has '{0}' lines, which is fewer than the provided line number '{1}'. The given file has '{0}' lines, which is fewer than the provided line number '{1}'. @@ -883,8 +893,8 @@ - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? @@ -897,6 +907,11 @@ Cannot intercept a call in file with path '{0}' because multiple files in the compilation have this path. + + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + + Cannot intercept: compilation does not contain a file with path '{0}'. Cannot intercept: compilation does not contain a file with path '{0}'. @@ -912,6 +927,11 @@ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'. + + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + + Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. @@ -2307,6 +2327,16 @@ Die Verwendung der Variablen in diesem Kontext kann dazu führen, dass referenzierte Variablen außerhalb ihres Deklarationsbereichs verfügbar gemacht werden. + + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + + + + Signatures of interceptable and interceptor methods do not match. + Signatures of interceptable and interceptor methods do not match. + + InterpolatedStringHandlerArgument has no effect when applied to lambda parameters and will be ignored at the call site. Das InterpolatedStringHandlerArgument hat bei Anwendung auf Lambdaparameter keine Auswirkungen und wird am Aufrufstandort ignoriert. @@ -2357,6 +2387,26 @@ Die Branches des bedingten ref-Operators verweisen auf Variablen mit inkompatiblen Deklarationsbereichen. + + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + + + + Nullability of reference types in type of parameter doesn't match interceptable method. + Nullability of reference types in type of parameter doesn't match interceptable method. + + + + Nullability of reference types in return type doesn't match interceptable method '{0}'. + Nullability of reference types in return type doesn't match interceptable method '{0}'. + + + + Nullability of reference types in return type doesn't match interceptable method. + Nullability of reference types in return type doesn't match interceptable method. + + Required member '{0}' should not be attributed with 'ObsoleteAttribute' unless the containing type is obsolete or all constructors are obsolete. Der erforderliche Member "{0}" darf nicht mit "ObsoleteAttribute" attributiert werden, es sei denn, der enthaltende Typ ist veraltet oder alle Konstruktoren sind veraltet. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index bf8028424df7b..29c115cbccba0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -487,6 +487,11 @@ "{0}" está implementado de forma explícita más de una vez. + + The indicated call is intercepted multiple times. + The indicated call is intercepted multiple times. + + '{0}' is already listed in the interface list on type '{2}' as '{1}'. "{0}" ya se muestra en la lista de interfaces en el tipo "{2}" como "{1}". @@ -862,6 +867,11 @@ Interceptor cannot have a 'null' file path. + + Line and character numbers provided to InterceptsLocationAttribute must be positive. + Line and character numbers provided to InterceptsLocationAttribute must be positive. + + The given file has '{0}' lines, which is fewer than the provided line number '{1}'. The given file has '{0}' lines, which is fewer than the provided line number '{1}'. @@ -883,8 +893,8 @@ - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? @@ -897,6 +907,11 @@ Cannot intercept a call in file with path '{0}' because multiple files in the compilation have this path. + + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + + Cannot intercept: compilation does not contain a file with path '{0}'. Cannot intercept: compilation does not contain a file with path '{0}'. @@ -912,6 +927,11 @@ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'. + + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + + Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. @@ -2307,6 +2327,16 @@ Usar la variable en este contexto puede exponer variables a las que se hace referencia fuera de su ámbito de declaración + + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + + + + Signatures of interceptable and interceptor methods do not match. + Signatures of interceptable and interceptor methods do not match. + + InterpolatedStringHandlerArgument has no effect when applied to lambda parameters and will be ignored at the call site. El argumento InterpolatedStringHandlerArgument no tiene ningún efecto cuando se aplica a parámetros lambda y se omitirá en el sitio de llamada. @@ -2357,6 +2387,26 @@ Las ramas del operador condicional ref hacen referencia a variables con ámbitos de declaración incompatibles + + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + + + + Nullability of reference types in type of parameter doesn't match interceptable method. + Nullability of reference types in type of parameter doesn't match interceptable method. + + + + Nullability of reference types in return type doesn't match interceptable method '{0}'. + Nullability of reference types in return type doesn't match interceptable method '{0}'. + + + + Nullability of reference types in return type doesn't match interceptable method. + Nullability of reference types in return type doesn't match interceptable method. + + Required member '{0}' should not be attributed with 'ObsoleteAttribute' unless the containing type is obsolete or all constructors are obsolete. El miembro requerido '{0}' no se debe atribuir con 'ObsoleteAttribute' a menos que el tipo contenedor esté obsoleto o todos los constructores estén obsoletos. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 2ed1cda05a4cc..9755c94df35ef 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -487,6 +487,11 @@ '{0}' est implémenté explicitement plusieurs fois. + + The indicated call is intercepted multiple times. + The indicated call is intercepted multiple times. + + '{0}' is already listed in the interface list on type '{2}' as '{1}'. '{0}' est déjà listé dans la liste d'interfaces du type '{2}' en tant que '{1}'. @@ -862,6 +867,11 @@ Interceptor cannot have a 'null' file path. + + Line and character numbers provided to InterceptsLocationAttribute must be positive. + Line and character numbers provided to InterceptsLocationAttribute must be positive. + + The given file has '{0}' lines, which is fewer than the provided line number '{1}'. The given file has '{0}' lines, which is fewer than the provided line number '{1}'. @@ -883,8 +893,8 @@ - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? @@ -897,6 +907,11 @@ Cannot intercept a call in file with path '{0}' because multiple files in the compilation have this path. + + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + + Cannot intercept: compilation does not contain a file with path '{0}'. Cannot intercept: compilation does not contain a file with path '{0}'. @@ -912,6 +927,11 @@ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'. + + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + + Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. @@ -2307,6 +2327,16 @@ Utiliser la variable dans ce contexte peut exposer des variables de référence en dehors de leur étendue de déclaration + + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + + + + Signatures of interceptable and interceptor methods do not match. + Signatures of interceptable and interceptor methods do not match. + + InterpolatedStringHandlerArgument has no effect when applied to lambda parameters and will be ignored at the call site. InterpolatedStringHandlerArgument n’a aucun effet lorsqu’il est appliqué aux paramètres lambda et qu’il est ignoré sur le site d’appel. @@ -2357,6 +2387,26 @@ Les branches d'un opérateur conditionnel ref font référence à des variables ayant des étendues de déclaration incompatibles + + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + + + + Nullability of reference types in type of parameter doesn't match interceptable method. + Nullability of reference types in type of parameter doesn't match interceptable method. + + + + Nullability of reference types in return type doesn't match interceptable method '{0}'. + Nullability of reference types in return type doesn't match interceptable method '{0}'. + + + + Nullability of reference types in return type doesn't match interceptable method. + Nullability of reference types in return type doesn't match interceptable method. + + Required member '{0}' should not be attributed with 'ObsoleteAttribute' unless the containing type is obsolete or all constructors are obsolete. Le membre obligatoire '{0}' ne doit pas être attribué avec 'ObsoleteAttribute', sauf si le type conteneur est obsolète ou si tous les constructeurs sont obsolètes. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 5e7c8d42b58c9..dc9e56dadc4da 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -487,6 +487,11 @@ '{0}' è implementato più di una volta in modo esplicito. + + The indicated call is intercepted multiple times. + The indicated call is intercepted multiple times. + + '{0}' is already listed in the interface list on type '{2}' as '{1}'. '{0}' è già incluso nell'elenco di interfacce nel tipo '{2}' come '{1}'. @@ -862,6 +867,11 @@ Interceptor cannot have a 'null' file path. + + Line and character numbers provided to InterceptsLocationAttribute must be positive. + Line and character numbers provided to InterceptsLocationAttribute must be positive. + + The given file has '{0}' lines, which is fewer than the provided line number '{1}'. The given file has '{0}' lines, which is fewer than the provided line number '{1}'. @@ -883,8 +893,8 @@ - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? @@ -897,6 +907,11 @@ Cannot intercept a call in file with path '{0}' because multiple files in the compilation have this path. + + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + + Cannot intercept: compilation does not contain a file with path '{0}'. Cannot intercept: compilation does not contain a file with path '{0}'. @@ -912,6 +927,11 @@ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'. + + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + + Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. @@ -2307,6 +2327,16 @@ L'uso di variabili in questo contesto potrebbe esporre le variabili a cui si fa riferimento al di fuori dell'ambito della dichiarazione + + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + + + + Signatures of interceptable and interceptor methods do not match. + Signatures of interceptable and interceptor methods do not match. + + InterpolatedStringHandlerArgument has no effect when applied to lambda parameters and will be ignored at the call site. InterpolatedStringHandlerArgument non ha alcun effetto se viene applicato ai parametri lambda e verrà ignorato nel sito di chiamata. @@ -2357,6 +2387,26 @@ I rami dell'operatore condizionale di riferimento fanno riferimento a variabili con ambiti di dichiarazione incompatibili + + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + + + + Nullability of reference types in type of parameter doesn't match interceptable method. + Nullability of reference types in type of parameter doesn't match interceptable method. + + + + Nullability of reference types in return type doesn't match interceptable method '{0}'. + Nullability of reference types in return type doesn't match interceptable method '{0}'. + + + + Nullability of reference types in return type doesn't match interceptable method. + Nullability of reference types in return type doesn't match interceptable method. + + Required member '{0}' should not be attributed with 'ObsoleteAttribute' unless the containing type is obsolete or all constructors are obsolete. Al membro obbligatorio '{0}' non deve essere attribuito 'ObsoleteAttribute' a meno che il tipo contenitore sia obsoleto o che tutti i costruttori siano obsoleti. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 5e23eba1ae300..e667c94a36830 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -487,6 +487,11 @@ '{0}' が複数回、明示的に実装されています。 + + The indicated call is intercepted multiple times. + The indicated call is intercepted multiple times. + + '{0}' is already listed in the interface list on type '{2}' as '{1}'. '{0}' は、型 '{2}' のインターフェイス リストに '{1}' として既に指定されています。 @@ -862,6 +867,11 @@ Interceptor cannot have a 'null' file path. + + Line and character numbers provided to InterceptsLocationAttribute must be positive. + Line and character numbers provided to InterceptsLocationAttribute must be positive. + + The given file has '{0}' lines, which is fewer than the provided line number '{1}'. The given file has '{0}' lines, which is fewer than the provided line number '{1}'. @@ -883,8 +893,8 @@ - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? @@ -897,6 +907,11 @@ Cannot intercept a call in file with path '{0}' because multiple files in the compilation have this path. + + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + + Cannot intercept: compilation does not contain a file with path '{0}'. Cannot intercept: compilation does not contain a file with path '{0}'. @@ -912,6 +927,11 @@ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'. + + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + + Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. @@ -2307,6 +2327,16 @@ このコンテキストでの変数の使用は、参照される変数が宣言のスコープ外に公開される可能性があります + + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + + + + Signatures of interceptable and interceptor methods do not match. + Signatures of interceptable and interceptor methods do not match. + + InterpolatedStringHandlerArgument has no effect when applied to lambda parameters and will be ignored at the call site. ラムダ パラメーターに適用しても InterpolatedStringHandlerArgument は効果がありません。呼び出しサイトでは無視されます。 @@ -2357,6 +2387,26 @@ ref 条件演算子のブランチでは、互換性のない宣言スコープを持つ変数を参照します + + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + + + + Nullability of reference types in type of parameter doesn't match interceptable method. + Nullability of reference types in type of parameter doesn't match interceptable method. + + + + Nullability of reference types in return type doesn't match interceptable method '{0}'. + Nullability of reference types in return type doesn't match interceptable method '{0}'. + + + + Nullability of reference types in return type doesn't match interceptable method. + Nullability of reference types in return type doesn't match interceptable method. + + Required member '{0}' should not be attributed with 'ObsoleteAttribute' unless the containing type is obsolete or all constructors are obsolete. 必要なメンバー '{0}' は、包含する型が古い形式であるか、すべてのコンストラクターが古い形式でない限り、属性 'ObsoleteAttribute' が必要ありません。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 50df86a77957d..1de7113926be4 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -487,6 +487,11 @@ '{0}'은(는) 두 번 이상 명시적으로 구현됩니다. + + The indicated call is intercepted multiple times. + The indicated call is intercepted multiple times. + + '{0}' is already listed in the interface list on type '{2}' as '{1}'. '{0}'은(는) '{2}' 형식에 대한 인터페이스 목록에 '{1}'(으)로 이미 나열되어 있습니다. @@ -862,6 +867,11 @@ Interceptor cannot have a 'null' file path. + + Line and character numbers provided to InterceptsLocationAttribute must be positive. + Line and character numbers provided to InterceptsLocationAttribute must be positive. + + The given file has '{0}' lines, which is fewer than the provided line number '{1}'. The given file has '{0}' lines, which is fewer than the provided line number '{1}'. @@ -883,8 +893,8 @@ - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? @@ -897,6 +907,11 @@ Cannot intercept a call in file with path '{0}' because multiple files in the compilation have this path. + + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + + Cannot intercept: compilation does not contain a file with path '{0}'. Cannot intercept: compilation does not contain a file with path '{0}'. @@ -912,6 +927,11 @@ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'. + + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + + Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. @@ -2307,6 +2327,16 @@ 이 컨텍스트에서 변수를 사용하면 선언 범위 외부에서 참조된 변수가 노출될 수 있습니다. + + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + + + + Signatures of interceptable and interceptor methods do not match. + Signatures of interceptable and interceptor methods do not match. + + InterpolatedStringHandlerArgument has no effect when applied to lambda parameters and will be ignored at the call site. InterpolatedStringHandlerArgument는 람다 매개 변수에 적용할 때 효과가 없으며 호출 사이트에서 무시됩니다. @@ -2357,6 +2387,26 @@ ref 조건부 연산자의 분기는 선언 범위가 호환되지 않는 변수를 참조합니다. + + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + + + + Nullability of reference types in type of parameter doesn't match interceptable method. + Nullability of reference types in type of parameter doesn't match interceptable method. + + + + Nullability of reference types in return type doesn't match interceptable method '{0}'. + Nullability of reference types in return type doesn't match interceptable method '{0}'. + + + + Nullability of reference types in return type doesn't match interceptable method. + Nullability of reference types in return type doesn't match interceptable method. + + Required member '{0}' should not be attributed with 'ObsoleteAttribute' unless the containing type is obsolete or all constructors are obsolete. 필수 구성원 '{0}'은(는) 포함하는 유형이 더 이상 사용되지 않거나 모든 생성자가 사용되지 않는 경우가 아니면 'ObsoleteAttribute'로 특성을 지정해서는 안 됩니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index d916b097ef646..b7806b07f7340 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -487,6 +487,11 @@ Element „{0}” jest jawnie zaimplementowany więcej niż raz. + + The indicated call is intercepted multiple times. + The indicated call is intercepted multiple times. + + '{0}' is already listed in the interface list on type '{2}' as '{1}'. Element „{0}” znajduje się już na liście interfejsów typu „{2}” jako „{1}”. @@ -862,6 +867,11 @@ Interceptor cannot have a 'null' file path. + + Line and character numbers provided to InterceptsLocationAttribute must be positive. + Line and character numbers provided to InterceptsLocationAttribute must be positive. + + The given file has '{0}' lines, which is fewer than the provided line number '{1}'. The given file has '{0}' lines, which is fewer than the provided line number '{1}'. @@ -883,8 +893,8 @@ - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? @@ -897,6 +907,11 @@ Cannot intercept a call in file with path '{0}' because multiple files in the compilation have this path. + + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + + Cannot intercept: compilation does not contain a file with path '{0}'. Cannot intercept: compilation does not contain a file with path '{0}'. @@ -912,6 +927,11 @@ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'. + + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + + Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. @@ -2307,6 +2327,16 @@ Nie można używać zmiennej w tym kontekście, ponieważ może uwidaczniać odwoływane zmienne poza ich zakresem deklaracji + + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + + + + Signatures of interceptable and interceptor methods do not match. + Signatures of interceptable and interceptor methods do not match. + + InterpolatedStringHandlerArgument has no effect when applied to lambda parameters and will be ignored at the call site. Argument InterpolatedStringHandlerArgument nie odniesie żadnego skutku po zastosowaniu do parametrów lambda i zostanie zignorowany w lokacji wywołania. @@ -2357,6 +2387,26 @@ Gałęzie operatora warunkowego ref nie mogą przywoływać zmiennych z niezgodnymi zakresami deklaracji + + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + + + + Nullability of reference types in type of parameter doesn't match interceptable method. + Nullability of reference types in type of parameter doesn't match interceptable method. + + + + Nullability of reference types in return type doesn't match interceptable method '{0}'. + Nullability of reference types in return type doesn't match interceptable method '{0}'. + + + + Nullability of reference types in return type doesn't match interceptable method. + Nullability of reference types in return type doesn't match interceptable method. + + Required member '{0}' should not be attributed with 'ObsoleteAttribute' unless the containing type is obsolete or all constructors are obsolete. Wymagana składowa „{0}” nie powinna mieć atrybutu „ObsoleteAttribute”, chyba że zawierający typ jest przestarzały lub wszystkie konstruktory są przestarzałe. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 1e2585b09173c..3d9101ac710db 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -487,6 +487,11 @@ '{0}' é implementado explicitamente mais de uma vez. + + The indicated call is intercepted multiple times. + The indicated call is intercepted multiple times. + + '{0}' is already listed in the interface list on type '{2}' as '{1}'. '{0}' já está listado na lista de interfaces no tipo '{2}' como '{1}'. @@ -862,6 +867,11 @@ Interceptor cannot have a 'null' file path. + + Line and character numbers provided to InterceptsLocationAttribute must be positive. + Line and character numbers provided to InterceptsLocationAttribute must be positive. + + The given file has '{0}' lines, which is fewer than the provided line number '{1}'. The given file has '{0}' lines, which is fewer than the provided line number '{1}'. @@ -883,8 +893,8 @@ - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? @@ -897,6 +907,11 @@ Cannot intercept a call in file with path '{0}' because multiple files in the compilation have this path. + + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + + Cannot intercept: compilation does not contain a file with path '{0}'. Cannot intercept: compilation does not contain a file with path '{0}'. @@ -912,6 +927,11 @@ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'. + + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + + Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. @@ -2307,6 +2327,16 @@ O uso de variável neste contexto pode expor variáveis referenciadas fora de seu escopo de declaração + + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + + + + Signatures of interceptable and interceptor methods do not match. + Signatures of interceptable and interceptor methods do not match. + + InterpolatedStringHandlerArgument has no effect when applied to lambda parameters and will be ignored at the call site. InterpolatedStringHandlerArgument não tem efeito quando aplicado a parâmetros lambda e será ignorado no local de chamada. @@ -2357,6 +2387,26 @@ As ramificações do operador condicional ref referem-se a variáveis com escopos de declaração incompatíveis + + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + + + + Nullability of reference types in type of parameter doesn't match interceptable method. + Nullability of reference types in type of parameter doesn't match interceptable method. + + + + Nullability of reference types in return type doesn't match interceptable method '{0}'. + Nullability of reference types in return type doesn't match interceptable method '{0}'. + + + + Nullability of reference types in return type doesn't match interceptable method. + Nullability of reference types in return type doesn't match interceptable method. + + Required member '{0}' should not be attributed with 'ObsoleteAttribute' unless the containing type is obsolete or all constructors are obsolete. Os membros requeridos '{0}' não devem ser atribuídos com 'ObsoleteAttribute', a menos que o tipo que o contém seja obsoleto ou que todos os construtores estejam obsoletos. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index c2ecd6729ad33..b04bbaf79e6b8 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -487,6 +487,11 @@ "{0}" явно реализуется больше одного раза. + + The indicated call is intercepted multiple times. + The indicated call is intercepted multiple times. + + '{0}' is already listed in the interface list on type '{2}' as '{1}'. "{0}" уже указан в списке интерфейсов в типе "{2}" в виде "{1}". @@ -862,6 +867,11 @@ Interceptor cannot have a 'null' file path. + + Line and character numbers provided to InterceptsLocationAttribute must be positive. + Line and character numbers provided to InterceptsLocationAttribute must be positive. + + The given file has '{0}' lines, which is fewer than the provided line number '{1}'. The given file has '{0}' lines, which is fewer than the provided line number '{1}'. @@ -883,8 +893,8 @@ - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? @@ -897,6 +907,11 @@ Cannot intercept a call in file with path '{0}' because multiple files in the compilation have this path. + + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + + Cannot intercept: compilation does not contain a file with path '{0}'. Cannot intercept: compilation does not contain a file with path '{0}'. @@ -912,6 +927,11 @@ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'. + + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + + Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. @@ -2307,6 +2327,16 @@ Использование переменной в этом контексте может представить ссылочные переменные за пределами области их объявления. + + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + + + + Signatures of interceptable and interceptor methods do not match. + Signatures of interceptable and interceptor methods do not match. + + InterpolatedStringHandlerArgument has no effect when applied to lambda parameters and will be ignored at the call site. Атрибут InterpolatedStringHandlerArgument не действует при применении к лямбда-параметрам и будет проигнорирован на сайте вызова. @@ -2357,6 +2387,26 @@ Ветви условного оператора ref ссылаются на переменные с несовместимыми областями объявления. + + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + + + + Nullability of reference types in type of parameter doesn't match interceptable method. + Nullability of reference types in type of parameter doesn't match interceptable method. + + + + Nullability of reference types in return type doesn't match interceptable method '{0}'. + Nullability of reference types in return type doesn't match interceptable method '{0}'. + + + + Nullability of reference types in return type doesn't match interceptable method. + Nullability of reference types in return type doesn't match interceptable method. + + Required member '{0}' should not be attributed with 'ObsoleteAttribute' unless the containing type is obsolete or all constructors are obsolete. У обязательного элемента "{0}" не должно быть атрибута "ObsoleteAttribute", если содержащий тип или все конструкторы не являются устаревшими. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 18e4125a41914..9d38998f27a14 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -487,6 +487,11 @@ '{0}' bir defadan fazla açıkça uygulanmış. + + The indicated call is intercepted multiple times. + The indicated call is intercepted multiple times. + + '{0}' is already listed in the interface list on type '{2}' as '{1}'. '{0}', '{2}' türünün arabirim listesinde zaten '{1}' olarak listelenmiş. @@ -862,6 +867,11 @@ Interceptor cannot have a 'null' file path. + + Line and character numbers provided to InterceptsLocationAttribute must be positive. + Line and character numbers provided to InterceptsLocationAttribute must be positive. + + The given file has '{0}' lines, which is fewer than the provided line number '{1}'. The given file has '{0}' lines, which is fewer than the provided line number '{1}'. @@ -883,8 +893,8 @@ - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? @@ -897,6 +907,11 @@ Cannot intercept a call in file with path '{0}' because multiple files in the compilation have this path. + + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + + Cannot intercept: compilation does not contain a file with path '{0}'. Cannot intercept: compilation does not contain a file with path '{0}'. @@ -912,6 +927,11 @@ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'. + + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + + Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. @@ -2307,6 +2327,16 @@ Bu bağlamda değişken kullanımı, başvurulan değişkenleri bildirim kapsamının dışında gösterebilir. + + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + + + + Signatures of interceptable and interceptor methods do not match. + Signatures of interceptable and interceptor methods do not match. + + InterpolatedStringHandlerArgument has no effect when applied to lambda parameters and will be ignored at the call site. InterpolatedStringHandlerArgument, lambda parametrelerine uygulandığında hiçbir etkiye sahip değildir ve çağrı sitesinde yok sayılır. @@ -2357,6 +2387,26 @@ Ref koşullu operatörünün dalları, uyumsuz bildirim kapsamlarına sahip değişkenlere başvurur + + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + + + + Nullability of reference types in type of parameter doesn't match interceptable method. + Nullability of reference types in type of parameter doesn't match interceptable method. + + + + Nullability of reference types in return type doesn't match interceptable method '{0}'. + Nullability of reference types in return type doesn't match interceptable method '{0}'. + + + + Nullability of reference types in return type doesn't match interceptable method. + Nullability of reference types in return type doesn't match interceptable method. + + Required member '{0}' should not be attributed with 'ObsoleteAttribute' unless the containing type is obsolete or all constructors are obsolete. Gerekli '{0}' üyesine, kapsayan tür veya tüm oluşturucular artık kullanılmadığı sürece 'ObsoleteAttribute' özniteliği atanmamalıdır. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index c96e9ba2d67e7..399c2fb980e82 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -487,6 +487,11 @@ 多次显式实现“{0}”。 + + The indicated call is intercepted multiple times. + The indicated call is intercepted multiple times. + + '{0}' is already listed in the interface list on type '{2}' as '{1}'. “{0}”已作为“{1}”列入类型“{2}”的接口列表中。 @@ -862,6 +867,11 @@ Interceptor cannot have a 'null' file path. + + Line and character numbers provided to InterceptsLocationAttribute must be positive. + Line and character numbers provided to InterceptsLocationAttribute must be positive. + + The given file has '{0}' lines, which is fewer than the provided line number '{1}'. The given file has '{0}' lines, which is fewer than the provided line number '{1}'. @@ -883,8 +893,8 @@ - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? @@ -897,6 +907,11 @@ Cannot intercept a call in file with path '{0}' because multiple files in the compilation have this path. + + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + + Cannot intercept: compilation does not contain a file with path '{0}'. Cannot intercept: compilation does not contain a file with path '{0}'. @@ -912,6 +927,11 @@ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'. + + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + + Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. @@ -2307,6 +2327,16 @@ 在此上下文中使用变量可能会在变量声明范围以外公开所引用的变量 + + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + + + + Signatures of interceptable and interceptor methods do not match. + Signatures of interceptable and interceptor methods do not match. + + InterpolatedStringHandlerArgument has no effect when applied to lambda parameters and will be ignored at the call site. 应用于 lambda 参数时,InterpolatedStringHandlerArgument 不起任何作用,并将在调用站点被忽略。 @@ -2357,6 +2387,26 @@ ref 条件运算符的分支引用具有不兼容声明范围的变量 + + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + + + + Nullability of reference types in type of parameter doesn't match interceptable method. + Nullability of reference types in type of parameter doesn't match interceptable method. + + + + Nullability of reference types in return type doesn't match interceptable method '{0}'. + Nullability of reference types in return type doesn't match interceptable method '{0}'. + + + + Nullability of reference types in return type doesn't match interceptable method. + Nullability of reference types in return type doesn't match interceptable method. + + Required member '{0}' should not be attributed with 'ObsoleteAttribute' unless the containing type is obsolete or all constructors are obsolete. 除非包含类型已过时或所有构造函数已过时,否则不应将所需的成员'{0}'归属于 'ObsoleteAttribute'。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 6e078b0b48a7c..ea592d4d48bd5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -487,6 +487,11 @@ '{0}' 已明確實作多次。 + + The indicated call is intercepted multiple times. + The indicated call is intercepted multiple times. + + '{0}' is already listed in the interface list on type '{2}' as '{1}'. '{0}' 已列於類型 '{2}' 的介面清單中,名稱為 '{1}'。 @@ -862,6 +867,11 @@ Interceptor cannot have a 'null' file path. + + Line and character numbers provided to InterceptsLocationAttribute must be positive. + Line and character numbers provided to InterceptsLocationAttribute must be positive. + + The given file has '{0}' lines, which is fewer than the provided line number '{1}'. The given file has '{0}' lines, which is fewer than the provided line number '{1}'. @@ -883,8 +893,8 @@ - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. - The provided character number does not refer to the start of method name token '{0}'. Consider using character number '{1}' instead. + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? + The provided line and character number does not refer to the start of token '{0}'. Did you mean to use line '{1}' and character '{2}'? @@ -897,6 +907,11 @@ Cannot intercept a call in file with path '{0}' because multiple files in the compilation have this path. + + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + Cannot intercept call with '{0}' because it is not accessible within '{1}'. + + Cannot intercept: compilation does not contain a file with path '{0}'. Cannot intercept: compilation does not contain a file with path '{0}'. @@ -912,6 +927,11 @@ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'. + + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + Cannot intercept call to '{0}' with '{1}' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + + Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. Cannot intercept method '{0}' with interceptor '{1}' because the signatures do not match. @@ -2307,6 +2327,16 @@ 在此內容中使用變數,可能會將參考的變數公開在其宣告範圍外 + + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + Intercepting a call to '{0}' with interceptor '{1}', but the signatures do not match. + + + + Signatures of interceptable and interceptor methods do not match. + Signatures of interceptable and interceptor methods do not match. + + InterpolatedStringHandlerArgument has no effect when applied to lambda parameters and will be ignored at the call site. InterpolatedStringHandlerArgument 在套用至 Lambda 參數時沒有效果,將於呼叫網站忽略。 @@ -2357,6 +2387,26 @@ Ref 條件運算子的分支參考具有不相容宣告範圍的變數 + + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + Nullability of reference types in type of parameter '{0}' doesn't match interceptable method '{1}'. + + + + Nullability of reference types in type of parameter doesn't match interceptable method. + Nullability of reference types in type of parameter doesn't match interceptable method. + + + + Nullability of reference types in return type doesn't match interceptable method '{0}'. + Nullability of reference types in return type doesn't match interceptable method '{0}'. + + + + Nullability of reference types in return type doesn't match interceptable method. + Nullability of reference types in return type doesn't match interceptable method. + + Required member '{0}' should not be attributed with 'ObsoleteAttribute' unless the containing type is obsolete or all constructors are obsolete. 除非包含的類型已過時或所有建構函式已過時,否則必要成員 '{0}' 的屬性不應為 'ObsoleteAttribute'。 diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs index c2c25f3df1016..602f71d3280ad 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs @@ -2,6 +2,7 @@ // 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.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; @@ -12,46 +13,30 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics; public class InterceptorsTests : CSharpTestBase { - // PROTOTYPE(ic): test a case where the original method has type parameter constraints. - // PROTOTYPE(ic): for now we will just completely disallow type parameters in the interceptor. - // PROTOTYPE(ic): test where interceptable is a constructed method or a retargeting method. // PROTOTYPE(ic): Ensure that all `MethodSymbol.IsInterceptable` implementations have test coverage. - // PROTOTYPE(ic): test where there are differences between 'scoped' modifiers and '[UnscopedRef]' attributes // PROTOTYPE(ic): Possible test cases: // // * Intercept instance method with instance method in same class, base class, derived class // * Intercept with extern method - // * Intercept with abstract or interface method // * Intercept an abstract or interface method // * Intercept a virtual or overridden method // * Intercept a non-extension call to a static method with a static method when one or both are extension methods // * Intercept a struct instance method with an extension method with by-value / by-ref this parameter // * An explicit interface implementation marked as interceptable - // * Intercept a generic method call when the type parameters are / are not substituted - // PROTOTYPE(ic): test a valid case with large numbers of interceptors. (EndToEndTests?) // PROTOTYPE(ic): test intercepting an extension method with a non-extension method. Perhaps should be an error for simplicity even if calling in non-reduced form. - // PROTOTYPE(ic): test when parameter names differ or appear in a different order in interceptor vs interceptable method. - - // PROTOTYPE(ic): duplicates - // PROTOTYPE(ic): intercept with instance method - // PROTOTYPE(ic): intercept with instance base method - - // PROTOTYPE(ic): test interceptable explicit interface implementation (should error). - // PROTOTYPE(ic): test interceptor with 'ref this' to match a struct interceptable method. - private static readonly (string, string) s_attributesSource = (""" namespace System.Runtime.CompilerServices; [AttributeUsage(AttributeTargets.Method)] public sealed class InterceptableAttribute : Attribute { } - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public sealed class InterceptsLocationAttribute : Attribute { - public InterceptsLocationAttribute(string filePath, int line, int column) + public InterceptsLocationAttribute(string filePath, int line, int character) { } } @@ -88,6 +73,29 @@ void verify(ModuleSymbol module) } } + [Fact] + public void SelfInterception() + { + var source = """ + using System.Runtime.CompilerServices; + using System; + + class C + { + public static void Main() + { + InterceptableMethod(); + } + + [Interceptable] + [InterceptsLocation("Program.cs", 8, 9)] + public static void InterceptableMethod() { Console.Write(1); } + } + """; + var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + [Fact] public void StaticInterceptable_StaticInterceptor_NoParameters() { @@ -116,7 +124,7 @@ class D verifier.VerifyDiagnostics(); } - [Fact(Skip = "PROTOTYPE(ic): produce an error here")] + [Fact] public void Accessibility_01() { var source = """ @@ -140,13 +148,17 @@ class D private static void Interceptor1() { Console.Write("interceptor 1"); } } """; - var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "interceptor 1", verify: Verification.Fails); - verifier.VerifyDiagnostics(); + var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); + comp.VerifyEmitDiagnostics( + // Program.cs(17,6): error CS27018: Cannot intercept because 'D.Interceptor1()' is not accessible within 'C.Main()'. + // [InterceptsLocation("Program.cs", 11, 9)] + Diagnostic(ErrorCode.ERR_InterceptorNotAccessible, @"InterceptsLocation(""Program.cs"", 11, 9)").WithArguments("D.Interceptor1()", "C.Main()").WithLocation(17, 6)); } [Fact] public void Accessibility_02() { + // An interceptor declared within a file-local type can intercept a call even if the call site can't normally refer to the file-local type. var source1 = """ using System.Runtime.CompilerServices; using System; @@ -178,6 +190,105 @@ file class D verifier.VerifyDiagnostics(); } + [Fact] + public void FileLocalAttributeDefinitions_01() + { + var source = """ + using System; + using System.Runtime.CompilerServices; + + C.M(); + + class C + { + [Interceptable] + public static void M() => throw null!; + } + + static class D + { + [InterceptsLocation("Program.cs", 4, 3)] + public static void M() + { + Console.Write(1); + } + } + + namespace System.Runtime.CompilerServices + { + file class InterceptableAttribute : Attribute { } + file class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int character) { } + } + } + """; + + var verifier = CompileAndVerify((source, "Program.cs"), expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void FileLocalAttributeDefinitions_02() + { + var source0 = """ + using System.Runtime.CompilerServices; + + public class C + { + [Interceptable] + public static void M() => throw null!; + } + + namespace System.Runtime.CompilerServices + { + file class InterceptableAttribute : Attribute { } + } + """; + + var source1 = """ + using System; + using System.Runtime.CompilerServices; + + C.M(); + + static class D + { + [InterceptsLocation("File1.cs", 4, 3)] + public static void M() + { + Console.Write(1); + } + } + + namespace System.Runtime.CompilerServices + { + file class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int character) { } + } + } + """; + + var verifier = CompileAndVerify(new[] { (source0, "File0.cs"), (source1, "File1.cs") }, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + + var comp0 = CreateCompilation((source0, "File0.cs")); + comp0.VerifyEmitDiagnostics(); + + var verifier1 = CompileAndVerify((source1, "File1.cs"), new[] { comp0.ToMetadataReference() }, expectedOutput: "1"); + verifier1.VerifyDiagnostics(); + + // PROTOTYPE(ic): https://github.com/dotnet/roslyn/issues/67079 + // We are generally treating file-local definitions in source as matching the names of well-known attributes. + // Once the type is emitted to metadata and read back in, we no longer recognize it as the same attribute due to name mangling. + var verifier1_1 = CompileAndVerify((source1, "File1.cs"), new[] { comp0.EmitToImageReference() }, expectedOutput: "1"); + verifier1_1.VerifyDiagnostics( + // File1.cs(8,6): warning CS27000: Call to 'C.M()' is intercepted, but the method is not marked with 'System.Runtime.CompilerServices.InterceptableAttribute'. + // [InterceptsLocation("File1.cs", 4, 3)] + Diagnostic(ErrorCode.WRN_CallNotInterceptable, @"InterceptsLocation(""File1.cs"", 4, 3)").WithArguments("C.M()").WithLocation(8, 6)); + } + [Fact] public void InterceptableExtensionMethod_InterceptorExtensionMethod() { @@ -272,9 +383,10 @@ static class D """; var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); comp.VerifyEmitDiagnostics( - // Program.cs(15,11): error CS27007: Cannot intercept method 'C.InterceptableMethod(string)' with interceptor 'D.Interceptor1(C, string)' because the signatures do not match. - // c.InterceptableMethod("call site"); - Diagnostic(ErrorCode.ERR_InterceptorSignatureMismatch, "InterceptableMethod").WithArguments("C.InterceptableMethod(string)", "D.Interceptor1(C, string)").WithLocation(15, 11)); + // Program.cs(21,6): error CS27007: Cannot intercept method 'C.InterceptableMethod(string)' with interceptor 'D.Interceptor1(C, string)' because the signatures do not match. + // [InterceptsLocation("Program.cs", 15, 11)] + Diagnostic(ErrorCode.ERR_InterceptorSignatureMismatch, @"InterceptsLocation(""Program.cs"", 15, 11)").WithArguments("C.InterceptableMethod(string)", "D.Interceptor1(C, string)").WithLocation(21, 6) + ); } [Fact] @@ -322,9 +434,165 @@ public static class Interceptor var comp = CreateCompilation(new[] { source0, source1, source2, s_attributesSource }); comp.VerifyEmitDiagnostics( - // Interceptor.cs(15,6): error CS27015: Cannot intercept a call in file with path 'Program.cs' because multiple files in the compilation have this path. + // Interceptor.cs(15,25): error CS27015: Cannot intercept a call in file with path 'Program.cs' because multiple files in the compilation have this path. // [InterceptsLocation("Program.cs", 5, 11)] - Diagnostic(ErrorCode.ERR_InterceptorNonUniquePath, @"InterceptsLocation(""Program.cs"", 5, 11)").WithArguments("Program.cs").WithLocation(15, 6)); + Diagnostic(ErrorCode.ERR_InterceptorNonUniquePath, @"""Program.cs""").WithArguments("Program.cs").WithLocation(15, 25)); + } + + [Fact] + public void DuplicateLocation_01() + { + var source = """ + using System.Runtime.CompilerServices; + + C.M(); + + class C + { + [Interceptable] + public static void M() { } + } + + class D + { + [InterceptsLocation("Program.cs", 3, 3)] + public static void M1() { } + + [InterceptsLocation("Program.cs", 3, 3)] + public static void M2() { } + } + """; + + var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); + comp.VerifyEmitDiagnostics( + // Program.cs(13,6): error CS27016: The indicated call is intercepted multiple times. + // [InterceptsLocation("Program.cs", 3, 3)] + Diagnostic(ErrorCode.ERR_DuplicateInterceptor, @"InterceptsLocation(""Program.cs"", 3, 3)").WithLocation(13, 6), + // Program.cs(16,6): error CS27016: The indicated call is intercepted multiple times. + // [InterceptsLocation("Program.cs", 3, 3)] + Diagnostic(ErrorCode.ERR_DuplicateInterceptor, @"InterceptsLocation(""Program.cs"", 3, 3)").WithLocation(16, 6)); + } + + [Fact] + public void DuplicateLocation_02() + { + var source0 = """ + using System.Runtime.CompilerServices; + + C.M(); + + class C + { + [Interceptable] + public static void M() { } + } + """; + + var source1 = """ + using System.Runtime.CompilerServices; + + class D1 + { + [InterceptsLocation("Program.cs", 3, 3)] + public static void M1() { } + } + """; + + var source2 = """ + using System.Runtime.CompilerServices; + + class D2 + { + [InterceptsLocation("Program.cs", 3, 3)] + public static void M1() { } + } + """; + + var comp = CreateCompilation(new[] { (source0, "Program.cs"), (source1, "File1.cs"), (source2, "File2.cs"), s_attributesSource }); + comp.VerifyEmitDiagnostics( + // File2.cs(5,6): error CS27016: The indicated call is intercepted multiple times. + // [InterceptsLocation("Program.cs", 3, 3)] + Diagnostic(ErrorCode.ERR_DuplicateInterceptor, @"InterceptsLocation(""Program.cs"", 3, 3)").WithLocation(5, 6), + // File1.cs(5,6): error CS27016: The indicated call is intercepted multiple times. + // [InterceptsLocation("Program.cs", 3, 3)] + Diagnostic(ErrorCode.ERR_DuplicateInterceptor, @"InterceptsLocation(""Program.cs"", 3, 3)").WithLocation(5, 6) + ); + } + + [Fact] + public void DuplicateLocation_03() + { + // InterceptsLocationAttribute is not considered to *duplicate* an interception, even if it is inherited. + var source = """ + using System.Runtime.CompilerServices; + using System; + + var d = new D(); + d.M(); + + class C + { + [Interceptable] + public void M() => throw null!; + + [InterceptsLocation("Program.cs", 5, 3)] + public virtual void Interceptor() => throw null!; + } + + class D : C + { + public override void Interceptor() => Console.Write(1); + } + + namespace System.Runtime.CompilerServices + { + [AttributeUsage(AttributeTargets.Method)] + public sealed class InterceptableAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int character) + { + } + } + } + """; + + var verifier = CompileAndVerify((source, "Program.cs"), expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void DuplicateLocation_04() + { + var source = """ + using System.Runtime.CompilerServices; + + C.M(); + + class C + { + [Interceptable] + public static void M() { } + } + + class D + { + [InterceptsLocation("Program.cs", 3, 3)] + [InterceptsLocation("Program.cs", 3, 3)] + public static void M1() { } + } + """; + + var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); + comp.VerifyEmitDiagnostics( + // Program.cs(13,6): error CS27016: The indicated call is intercepted multiple times. + // [InterceptsLocation("Program.cs", 3, 3)] + Diagnostic(ErrorCode.ERR_DuplicateInterceptor, @"InterceptsLocation(""Program.cs"", 3, 3)").WithLocation(13, 6), + // Program.cs(14,6): error CS27016: The indicated call is intercepted multiple times. + // [InterceptsLocation("Program.cs", 3, 3)] + Diagnostic(ErrorCode.ERR_DuplicateInterceptor, @"InterceptsLocation(""Program.cs"", 3, 3)").WithLocation(14, 6)); } [Fact] @@ -536,6 +804,34 @@ static class D verifier.VerifyDiagnostics(); } + [Fact] + public void InterceptableExtensionMethod_InterceptorStaticMethod() + { + var source = """ + using System.Runtime.CompilerServices; + using System; + + var c = new C(); + c.InterceptableMethod(); + + class C { } + + static class D + { + [Interceptable] + public static void InterceptableMethod(this C c) => throw null!; + + [InterceptsLocation("Program.cs", 5, 3)] + public static void Interceptor1(C c) => throw null!; + } + """; + var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); + comp.VerifyEmitDiagnostics( + // Program.cs(14,6): error CS27011: Interceptor must have a 'this' parameter matching parameter 'C c' on 'D.InterceptableMethod(C)'. + // [InterceptsLocation("Program.cs", 5, 3)] + Diagnostic(ErrorCode.ERR_InterceptorMustHaveMatchingThisParameter, @"InterceptsLocation(""Program.cs"", 5, 3)").WithArguments("C c", "D.InterceptableMethod(C)").WithLocation(14, 6)); + } + [Fact] public void InterceptableStaticMethod_InterceptorInstanceMethod() { @@ -600,94 +896,227 @@ static class D } [Fact] - public void InterceptableExtensionMethod_InterceptorExtensionMethod_Sequence() + public void ParameterNameDifference() { var source = """ using System.Runtime.CompilerServices; using System; - interface I1 { } - class C : I1 { } - - static class Program + class C { [Interceptable] - public static I1 InterceptableMethod(this I1 i1, string param) { Console.Write("interceptable " + param); return i1; } + public void InterceptableMethod(string s1) => throw null!; + } + static class Program + { public static void Main() { var c = new C(); - c.InterceptableMethod("call site") - .InterceptableMethod("call site"); + c.InterceptableMethod(s1: "1"); } } static class D { [InterceptsLocation("Program.cs", 15, 11)] - public static I1 Interceptor1(this I1 i1, string param) { Console.Write("interceptor " + param); return i1; } + public static void Interceptor1(this C c, string s2) { Console.Write(s2); } } """; - var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "interceptor call siteinterceptable call site"); + var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "1"); verifier.VerifyDiagnostics(); } [Fact] - public void InterceptableFromMetadata() + public void ParameterNamesInDifferentOrder() { - var source1 = """ + var source = """ using System.Runtime.CompilerServices; using System; - public class C + class C { [Interceptable] - public C InterceptableMethod(string param) { Console.Write("interceptable " + param); return this; } + public void InterceptableMethod(string s1, string s2) => throw null!; } - """; - - var source2 = """ - using System.Runtime.CompilerServices; - using System; static class Program { public static void Main() { var c = new C(); - c.InterceptableMethod("call site"); + c.InterceptableMethod("1", "2"); + c.InterceptableMethod(s2: "4", s1: "3"); } } static class D { - [InterceptsLocation("Program.cs", 9, 11)] - public static C Interceptor1(this C c, string param) { Console.Write("interceptor " + param); return c; } + [InterceptsLocation("Program.cs", 15, 11)] + [InterceptsLocation("Program.cs", 16, 11)] + public static void Interceptor1(this C c, string s2, string s1) { Console.Write(s2); Console.Write(s1); } } """; - - var comp1 = CreateCompilation(new[] { (source1, "File1.cs"), s_attributesSource }); - comp1.VerifyEmitDiagnostics(); - - var verifier = CompileAndVerify((source2, "Program.cs"), references: new[] { comp1.ToMetadataReference() }, expectedOutput: "interceptor call site"); + var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "1234"); verifier.VerifyDiagnostics(); } [Fact] - public void BadMethodKind() + public void AttributeArgumentLabels_01() { var source = """ using System.Runtime.CompilerServices; + using System; - static class Program + class C { [Interceptable] - public static void InterceptableMethod(string param) { } + public void InterceptableMethod() => throw null!; + } + static class Program + { public static void Main() { - InterceptableMethod(""); - Interceptor1(""); + var c = new C(); + c.InterceptableMethod(); + } + } + + static class D + { + [InterceptsLocation("Program.cs", character: 11, line: 15)] + public static void Interceptor1(this C c) { Console.Write(1); } + } + """; + var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void AttributeArgumentLabels_02() + { + var source = """ + using System.Runtime.CompilerServices; + + class C + { + [Interceptable] + public void InterceptableMethod() => throw null!; + } + + static class Program + { + public static void Main() + { + var c = new C(); + c.InterceptableMethod(); + } + } + + static class D + { + [InterceptsLocation("Program.cs", character: 1, line: 50)] // 1 + public static void Interceptor1(this C c) => throw null!; + } + """; + var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); + comp.VerifyDiagnostics( + // Program.cs(20,53): error CS27005: The given file has '22' lines, which is fewer than the provided line number '50'. + // [InterceptsLocation("Program.cs", character: 1, line: 50)] // 1 + Diagnostic(ErrorCode.ERR_InterceptorLineOutOfRange, "line: 50").WithArguments("22", "50").WithLocation(20, 53) + ); + } + + [Fact] + public void InterceptableExtensionMethod_InterceptorExtensionMethod_Sequence() + { + var source = """ + using System.Runtime.CompilerServices; + using System; + + interface I1 { } + class C : I1 { } + + static class Program + { + [Interceptable] + public static I1 InterceptableMethod(this I1 i1, string param) { Console.Write("interceptable " + param); return i1; } + + public static void Main() + { + var c = new C(); + c.InterceptableMethod("call site") + .InterceptableMethod("call site"); + } + } + + static class D + { + [InterceptsLocation("Program.cs", 15, 11)] + public static I1 Interceptor1(this I1 i1, string param) { Console.Write("interceptor " + param); return i1; } + } + """; + var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "interceptor call siteinterceptable call site"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void InterceptableFromMetadata() + { + var source1 = """ + using System.Runtime.CompilerServices; + using System; + + public class C + { + [Interceptable] + public C InterceptableMethod(string param) { Console.Write("interceptable " + param); return this; } + } + """; + + var source2 = """ + using System.Runtime.CompilerServices; + using System; + + static class Program + { + public static void Main() + { + var c = new C(); + c.InterceptableMethod("call site"); + } + } + + static class D + { + [InterceptsLocation("Program.cs", 9, 11)] + public static C Interceptor1(this C c, string param) { Console.Write("interceptor " + param); return c; } + } + """; + + var comp1 = CreateCompilation(new[] { (source1, "File1.cs"), s_attributesSource }); + comp1.VerifyEmitDiagnostics(); + + var verifier = CompileAndVerify((source2, "Program.cs"), references: new[] { comp1.ToMetadataReference() }, expectedOutput: "interceptor call site"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void InterceptsLocation_BadMethodKind() + { + var source = """ + using System.Runtime.CompilerServices; + + static class Program + { + [Interceptable] + public static void InterceptableMethod(string param) { } + + public static void Main() + { + InterceptableMethod(""); + Interceptor1(""); var lambda = [InterceptsLocation("Program.cs", 13, 8)] (string param) => { }; // 1 @@ -717,7 +1146,7 @@ public static string Prop } [Fact] - public void LocalFunctionInterceptable() + public void Interceptable_BadMethodKind() { var source = """ using System.Runtime.CompilerServices; @@ -730,21 +1159,33 @@ static class Program { public static void Main() { + var lambda = [Interceptable] (string param) => { }; // 1 + InterceptableMethod("call site"); - [Interceptable] + [Interceptable] // 2 static void InterceptableMethod(string param) { Console.Write("interceptable " + param); } } - [InterceptsLocation("Program.cs", 11, 9)] - public static void Interceptor1(string param) { Console.Write("interceptor " + param); } + public static string Prop + { + [Interceptable] // 3 + set { } + } } """; var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); comp.VerifyDiagnostics( - // Program.cs(13,10): error CS27008: An interceptable method must be an ordinary member method. - // [Interceptable] - Diagnostic(ErrorCode.ERR_InterceptableMethodMustBeOrdinary, "Interceptable").WithLocation(13, 10)); + // Program.cs(11,23): error CS27008: An interceptable method must be an ordinary member method. + // var lambda = [Interceptable] (string param) => { }; // 1 + Diagnostic(ErrorCode.ERR_InterceptableMethodMustBeOrdinary, "Interceptable").WithLocation(11, 23), + // Program.cs(15,10): error CS27008: An interceptable method must be an ordinary member method. + // [Interceptable] // 2 + Diagnostic(ErrorCode.ERR_InterceptableMethodMustBeOrdinary, "Interceptable").WithLocation(15, 10), + // Program.cs(21,10): error CS27008: An interceptable method must be an ordinary member method. + // [Interceptable] // 3 + Diagnostic(ErrorCode.ERR_InterceptableMethodMustBeOrdinary, "Interceptable").WithLocation(21, 10) + ); } [Fact] @@ -968,6 +1409,81 @@ static class D ); } + [Fact] + public void InterceptableGeneric_03() + { + var source = """ + using System.Runtime.CompilerServices; + using System; + + class C + { + [Interceptable] + public static void InterceptableMethod(T t) where T : class => throw null!; + } + + static class Program + { + public static void Main() + { + C.InterceptableMethod("1"); + } + } + + static class D + { + [InterceptsLocation("Program.cs", 14, 11)] + public static void Interceptor1(string s) { Console.Write(s); } + } + """; + var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void InterceptableGeneric_04() + { + // No interceptor can satisfy a signature like `void InterceptableMethod(T2 t2)` where `T2` is a method type argument. + // We would need to re-examine arity limitations and devise method type argument inference rules for interceptors to make this work. + var source = """ + using System.Runtime.CompilerServices; + using System; + + class C + { + [Interceptable] + public static void InterceptableMethod(T1 t) => throw null!; + } + + static class Program + { + public static void M(T2 t) + { + C.InterceptableMethod(t); + C.InterceptableMethod(t); + C.InterceptableMethod(t); + } + } + + static class D + { + [InterceptsLocation("Program.cs", 14, 11)] // 1 + [InterceptsLocation("Program.cs", 15, 11)] // 2 + [InterceptsLocation("Program.cs", 16, 11)] + public static void Interceptor1(object s) { Console.Write(s); } + } + """; + var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); + comp.VerifyEmitDiagnostics( + // Program.cs(22,6): error CS27007: Cannot intercept method 'C.InterceptableMethod(T2)' with interceptor 'D.Interceptor1(object)' because the signatures do not match. + // [InterceptsLocation("Program.cs", 14, 11)] // 1 + Diagnostic(ErrorCode.ERR_InterceptorSignatureMismatch, @"InterceptsLocation(""Program.cs"", 14, 11)").WithArguments("C.InterceptableMethod(T2)", "D.Interceptor1(object)").WithLocation(22, 6), + // Program.cs(23,6): error CS27007: Cannot intercept method 'C.InterceptableMethod(T2)' with interceptor 'D.Interceptor1(object)' because the signatures do not match. + // [InterceptsLocation("Program.cs", 15, 11)] // 2 + Diagnostic(ErrorCode.ERR_InterceptorSignatureMismatch, @"InterceptsLocation(""Program.cs"", 15, 11)").WithArguments("C.InterceptableMethod(T2)", "D.Interceptor1(object)").WithLocation(23, 6) + ); + } + [Fact] public void InterceptsLocationBadAttributeArguments_01() { @@ -1031,9 +1547,9 @@ static class D """; var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); comp.VerifyEmitDiagnostics( - // Program.cs(21,6): error CS27002: Cannot intercept: compilation does not contain a file with path 'BAD'. + // Program.cs(21,25): error CS27002: Cannot intercept: compilation does not contain a file with path 'BAD'. // [InterceptsLocation("BAD", 15, 11)] - Diagnostic(ErrorCode.ERR_InterceptorPathNotInCompilation, @"InterceptsLocation(""BAD"", 15, 11)").WithArguments("BAD").WithLocation(21, 6) + Diagnostic(ErrorCode.ERR_InterceptorPathNotInCompilation, @"""BAD""").WithArguments("BAD").WithLocation(21, 25) ); } @@ -1067,9 +1583,9 @@ static class D """; var comp = CreateCompilation(new[] { (source, "/Users/me/projects/Program.cs"), s_attributesSource }); comp.VerifyEmitDiagnostics( - // /Users/me/projects/Program.cs(21,6): error CS27003: Cannot intercept: compilation does not contain a file with path 'projects/Program.cs'. Did you mean to use path '/Users/me/projects/Program.cs'? - // [InterceptsLocation("projects/Program.cs", 15, 11)] - Diagnostic(ErrorCode.ERR_InterceptorPathNotInCompilationWithCandidate, @"InterceptsLocation(""projects/Program.cs"", 15, 11)").WithArguments("projects/Program.cs", "/Users/me/projects/Program.cs").WithLocation(21, 6) + // /Users/me/projects/Program.cs(21,25): error CS27003: Cannot intercept: compilation does not contain a file with path 'projects/Program.cs'. Did you mean to use path '/Users/me/projects/Program.cs'? + // [InterceptsLocation("projects/Program.cs", 15, 11)] + Diagnostic(ErrorCode.ERR_InterceptorPathNotInCompilationWithCandidate, @"""projects/Program.cs""").WithArguments("projects/Program.cs", "/Users/me/projects/Program.cs").WithLocation(21, 25) ); } @@ -1102,9 +1618,44 @@ static class D """; var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); comp.VerifyEmitDiagnostics( - // Program.cs(20,6): error CS27013: Interceptor cannot have a 'null' file path. + // Program.cs(20,25): error CS27013: Interceptor cannot have a 'null' file path. // [InterceptsLocation(null, 15, 11)] - Diagnostic(ErrorCode.ERR_InterceptorFilePathCannotBeNull, "InterceptsLocation(null, 15, 11)").WithLocation(20, 6) + Diagnostic(ErrorCode.ERR_InterceptorFilePathCannotBeNull, "null").WithLocation(20, 25) + ); + } + + [Fact] + public void InterceptsLocationBadPath_04() + { + var source = """ + using System.Runtime.CompilerServices; + using System; + + class C { } + + static class Program + { + [Interceptable] + public static C InterceptableMethod(this C c, string param) { Console.Write("interceptable " + param); return c; } + + public static void Main() + { + var c = new C(); + c.InterceptableMethod("call site"); + } + } + + static class D + { + [InterceptsLocation("program.cs", 15, 11)] + public static C Interceptor1(this C c, string param) { Console.Write("interceptor " + param); return c; } + } + """; + var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); + comp.VerifyEmitDiagnostics( + // Program.cs(20,25): error CS27002: Cannot intercept: compilation does not contain a file with path 'program.cs'. + // [InterceptsLocation("program.cs", 15, 11)] + Diagnostic(ErrorCode.ERR_InterceptorPathNotInCompilation, @"""program.cs""").WithArguments("program.cs").WithLocation(20, 25) ); } @@ -1143,12 +1694,12 @@ static class D // Program.cs(21,6): error CS27004: The provided line and character number does not refer to an interceptable method name, but rather to token '}'. // [InterceptsLocation("Program.cs", 25, 1)] Diagnostic(ErrorCode.ERR_InterceptorPositionBadToken, @"InterceptsLocation(""Program.cs"", 25, 1)").WithArguments("}").WithLocation(21, 6), - // Program.cs(22,6): error CS27005: The given file has '25' lines, which is fewer than the provided line number '26'. + // Program.cs(22,39): error CS27005: The given file has '25' lines, which is fewer than the provided line number '26'. // [InterceptsLocation("Program.cs", 26, 1)] - Diagnostic(ErrorCode.ERR_InterceptorLineOutOfRange, @"InterceptsLocation(""Program.cs"", 26, 1)").WithArguments("25", "26").WithLocation(22, 6), - // Program.cs(23,6): error CS27005: The given file has '25' lines, which is fewer than the provided line number '100'. + Diagnostic(ErrorCode.ERR_InterceptorLineOutOfRange, "26").WithArguments("25", "26").WithLocation(22, 39), + // Program.cs(23,39): error CS27005: The given file has '25' lines, which is fewer than the provided line number '100'. // [InterceptsLocation("Program.cs", 100, 1)] - Diagnostic(ErrorCode.ERR_InterceptorLineOutOfRange, @"InterceptsLocation(""Program.cs"", 100, 1)").WithArguments("25", "100").WithLocation(23, 6) + Diagnostic(ErrorCode.ERR_InterceptorLineOutOfRange, "100").WithArguments("25", "100").WithLocation(23, 39) ); } @@ -1187,12 +1738,12 @@ static class D // Program.cs(21,6): error CS27004: The provided line and character number does not refer to an interceptable method name, but rather to token '}'. // [InterceptsLocation("Program.cs", 16, 5)] Diagnostic(ErrorCode.ERR_InterceptorPositionBadToken, @"InterceptsLocation(""Program.cs"", 16, 5)").WithArguments("}").WithLocation(21, 6), - // Program.cs(22,6): error CS27006: The given line is '5' characters long, which is fewer than the provided character number '6'. + // Program.cs(22,43): error CS27006: The given line is '5' characters long, which is fewer than the provided character number '6'. // [InterceptsLocation("Program.cs", 16, 6)] - Diagnostic(ErrorCode.ERR_InterceptorCharacterOutOfRange, @"InterceptsLocation(""Program.cs"", 16, 6)").WithArguments("5", "6").WithLocation(22, 6), - // Program.cs(23,6): error CS27006: The given line is '5' characters long, which is fewer than the provided character number '1000'. + Diagnostic(ErrorCode.ERR_InterceptorCharacterOutOfRange, "6").WithArguments("5", "6").WithLocation(22, 43), + // Program.cs(23,43): error CS27006: The given line is '5' characters long, which is fewer than the provided character number '1000'. // [InterceptsLocation("Program.cs", 16, 1000)] - Diagnostic(ErrorCode.ERR_InterceptorCharacterOutOfRange, @"InterceptsLocation(""Program.cs"", 16, 1000)").WithArguments("5", "1000").WithLocation(23, 6) + Diagnostic(ErrorCode.ERR_InterceptorCharacterOutOfRange, "1000").WithArguments("5", "1000").WithLocation(23, 43) ); } @@ -1262,9 +1813,9 @@ static class D """; var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); comp.VerifyEmitDiagnostics( - // Program.cs(21,6): error CS27010: The provided character number does not refer to the start of method name token 'InterceptableMethod'. Consider using character number '11' instead. + // Program.cs(21,6): error CS27010: The provided line and character number does not refer to the start of token 'InterceptableMethod'. Did you mean to use line '15' and character '11'? // [InterceptsLocation("Program.cs", 15, 13)] - Diagnostic(ErrorCode.ERR_InterceptorMustReferToStartOfTokenPosition, @"InterceptsLocation(""Program.cs"", 15, 13)").WithArguments("InterceptableMethod", "11").WithLocation(21, 6) + Diagnostic(ErrorCode.ERR_InterceptorMustReferToStartOfTokenPosition, @"InterceptsLocation(""Program.cs"", 15, 13)").WithArguments("InterceptableMethod", "15", "11").WithLocation(21, 6) ); } @@ -1302,12 +1853,12 @@ static class CExt """; var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); comp.VerifyEmitDiagnostics( - // Program.cs(20,6): error CS27010: The provided character number does not refer to the start of method name token 'InterceptableMethod'. Consider using character number '13' instead. + // Program.cs(20,6): error CS27010: The provided line and character number does not refer to the start of token 'InterceptableMethod'. Did you mean to use line '12' and character '13'? // [InterceptsLocation("Program.cs", 12, 11)] // intercept spaces before 'InterceptableMethod' token - Diagnostic(ErrorCode.ERR_InterceptorMustReferToStartOfTokenPosition, @"InterceptsLocation(""Program.cs"", 12, 11)").WithArguments("InterceptableMethod", "13").WithLocation(20, 6), - // Program.cs(21,6): error CS27010: The provided character number does not refer to the start of method name token 'InterceptableMethod'. Consider using character number '11' instead. + Diagnostic(ErrorCode.ERR_InterceptorMustReferToStartOfTokenPosition, @"InterceptsLocation(""Program.cs"", 12, 11)").WithArguments("InterceptableMethod", "12", "13").WithLocation(20, 6), + // Program.cs(21,6): error CS27010: The provided line and character number does not refer to the start of token 'InterceptableMethod'. Did you mean to use line '14' and character '11'? // [InterceptsLocation("Program.cs", 14, 33)] // intercept spaces after 'InterceptableMethod' token - Diagnostic(ErrorCode.ERR_InterceptorMustReferToStartOfTokenPosition, @"InterceptsLocation(""Program.cs"", 14, 33)").WithArguments("InterceptableMethod", "11").WithLocation(21, 6) + Diagnostic(ErrorCode.ERR_InterceptorMustReferToStartOfTokenPosition, @"InterceptsLocation(""Program.cs"", 14, 33)").WithArguments("InterceptableMethod", "14", "11").WithLocation(21, 6) ); } @@ -1341,13 +1892,13 @@ static class CExt """; var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); comp.VerifyEmitDiagnostics( - // Program.cs(17,6): error CS27010: The provided character number does not refer to the start of method name token 'InterceptableMethod'. Consider using character number '11' instead. + // Program.cs(17,6): error CS27010: The provided line and character number does not refer to the start of token 'InterceptableMethod'. Did you mean to use line '11' and character '11'? // [InterceptsLocation("Program.cs", 11, 31)] // intercept comment after 'InterceptableMethod' token - Diagnostic(ErrorCode.ERR_InterceptorMustReferToStartOfTokenPosition, @"InterceptsLocation(""Program.cs"", 11, 31)").WithArguments("InterceptableMethod", "11").WithLocation(17, 6) + Diagnostic(ErrorCode.ERR_InterceptorMustReferToStartOfTokenPosition, @"InterceptsLocation(""Program.cs"", 11, 31)").WithArguments("InterceptableMethod", "11", "11").WithLocation(17, 6) ); } - [ConditionalFact(typeof(WindowsOnly), Reason = "PROTOTYPE(ic): diagnostic message differs depending on the size of line endings")] + [Fact] public void InterceptsLocationBadPosition_07() { var source = """ @@ -1377,12 +1928,65 @@ static class CExt { } """; - // PROTOTYPE(ic): the character suggested here is wrong. What should we do to give a useful diagnostic here? + var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); comp.VerifyEmitDiagnostics( - // Program.cs(19,6): error CS27010: The provided character number does not refer to the start of method name token 'InterceptableMethod'. Consider using character number '37' instead. + // Program.cs(19,6): error CS27010: The provided line and character number does not refer to the start of token 'InterceptableMethod'. Did you mean to use line '13' and character '13'? // [InterceptsLocation("Program.cs", 12, 13)] // intercept comment above 'InterceptableMethod' token - Diagnostic(ErrorCode.ERR_InterceptorMustReferToStartOfTokenPosition, @"InterceptsLocation(""Program.cs"", 12, 13)").WithArguments("InterceptableMethod", "37").WithLocation(19, 6) + Diagnostic(ErrorCode.ERR_InterceptorMustReferToStartOfTokenPosition, @"InterceptsLocation(""Program.cs"", 12, 13)").WithArguments("InterceptableMethod", "13", "13").WithLocation(19, 6) + ); + } + + [Fact] + public void InterceptsLocationBadPosition_08() + { + var source = """ + using System.Runtime.CompilerServices; + using System; + + class C { } + + static class Program + { + public static void Main() + { + var c = new C(); + c.InterceptableMethod("call site"); + } + + [Interceptable] + public static C InterceptableMethod(this C c, string param) { Console.Write("interceptable " + param); return c; } + + [InterceptsLocation("Program.cs", -1, 1)] // 1 + [InterceptsLocation("Program.cs", 1, -1)] // 2 + [InterceptsLocation("Program.cs", -1, -1)] // 3 + [InterceptsLocation("Program.cs", 0, 1)] // 4 + [InterceptsLocation("Program.cs", 1, 0)] // 5 + [InterceptsLocation("Program.cs", 0, 0)] // 6 + public static C Interceptor1(this C c, string param) { Console.Write("interceptor " + param); return c; } + } + """; + + var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); + comp.VerifyEmitDiagnostics( + // Program.cs(17,39): error CS27020: Line and character numbers provided to InterceptsLocationAttribute must be positive. + // [InterceptsLocation("Program.cs", -1, 1)] // 1 + Diagnostic(ErrorCode.ERR_InterceptorLineCharacterMustBePositive, "-1").WithLocation(17, 39), + // Program.cs(18,42): error CS27020: Line and character numbers provided to InterceptsLocationAttribute must be positive. + // [InterceptsLocation("Program.cs", 1, -1)] // 2 + Diagnostic(ErrorCode.ERR_InterceptorLineCharacterMustBePositive, "-1").WithLocation(18, 42), + // Program.cs(19,39): error CS27020: Line and character numbers provided to InterceptsLocationAttribute must be positive. + // [InterceptsLocation("Program.cs", -1, -1)] // 3 + Diagnostic(ErrorCode.ERR_InterceptorLineCharacterMustBePositive, "-1").WithLocation(19, 39), + // Program.cs(20,39): error CS27020: Line and character numbers provided to InterceptsLocationAttribute must be positive. + // [InterceptsLocation("Program.cs", 0, 1)] // 4 + Diagnostic(ErrorCode.ERR_InterceptorLineCharacterMustBePositive, "0").WithLocation(20, 39), + // Program.cs(21,42): error CS27020: Line and character numbers provided to InterceptsLocationAttribute must be positive. + // [InterceptsLocation("Program.cs", 1, 0)] // 5 + Diagnostic(ErrorCode.ERR_InterceptorLineCharacterMustBePositive, "0").WithLocation(21, 42), + // Program.cs(22,39): error CS27020: Line and character numbers provided to InterceptsLocationAttribute must be positive. + // [InterceptsLocation("Program.cs", 0, 0)] // 6 + Diagnostic(ErrorCode.ERR_InterceptorLineCharacterMustBePositive, "0").WithLocation(22, 39) ); } @@ -1416,9 +2020,9 @@ static class D """; var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); comp.VerifyEmitDiagnostics( - // Program.cs(15,11): error CS27007: Cannot intercept method 'Program.InterceptableMethod(I1, string)' with interceptor 'D.Interceptor1(I1, int)' because the signatures do not match. - // c.InterceptableMethod("call site"); - Diagnostic(ErrorCode.ERR_InterceptorSignatureMismatch, "InterceptableMethod").WithArguments("Program.InterceptableMethod(I1, string)", "D.Interceptor1(I1, int)").WithLocation(15, 11) + // Program.cs(21,6): error CS27007: Cannot intercept method 'Program.InterceptableMethod(I1, string)' with interceptor 'D.Interceptor1(I1, int)' because the signatures do not match. + // [InterceptsLocation("Program.cs", 15, 11)] + Diagnostic(ErrorCode.ERR_InterceptorSignatureMismatch, @"InterceptsLocation(""Program.cs"", 15, 11)").WithArguments("Program.InterceptableMethod(I1, string)", "D.Interceptor1(I1, int)").WithLocation(21, 6) ); } @@ -1498,11 +2102,652 @@ static class D } [Fact] - public void InterpolatedStringHandler_01() + public void SignatureMismatch_04() { - // Verify that interpolated string-related attributes on an intercepted call use the attributes from the interceptable method. - var code = """ -using System; + // Safe nullability difference + var source = """ + using System.Runtime.CompilerServices; + + class C + { + [Interceptable] + public string? InterceptableMethod(string param) => throw null!; + } + + static class Program + { + public static void Main() + { + var c = new C(); + c.InterceptableMethod("call site"); + } + } + + static class D + { + [InterceptsLocation("Program.cs", 14, 11)] + public static string Interceptor1(this C s, string? param) => throw null!; + } + """; + var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }, options: WithNullableEnable()); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void SignatureMismatch_05() + { + // Unsafe nullability difference + var source = """ + using System.Runtime.CompilerServices; + + class C + { + [Interceptable] + public void Method1(string? param1) => throw null!; + + [Interceptable] + public string Method2() => throw null!; + } + + static class Program + { + public static void Main() + { + var c = new C(); + c.Method1("call site"); + _ = c.Method2(); + } + } + + static class D + { + [InterceptsLocation("Program.cs", 17, 11)] // 1 + public static void Interceptor1(this C s, string param2) => throw null!; + + [InterceptsLocation("Program.cs", 18, 15)] // 2 + public static string? Interceptor2(this C s) => throw null!; + } + """; + + var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }, options: WithNullableEnable()); + comp.VerifyEmitDiagnostics( + // Program.cs(24,6): warning CS27022: Nullability of reference types in type of parameter 'param2' doesn't match interceptable method 'C.Method1(string?)'. + // [InterceptsLocation("Program.cs", 17, 11)] // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOnInterceptor, @"InterceptsLocation(""Program.cs"", 17, 11)").WithArguments("param2", "C.Method1(string?)").WithLocation(24, 6), + // Program.cs(27,6): warning CS27021: Nullability of reference types in return type doesn't match interceptable method 'C.Method2()'. + // [InterceptsLocation("Program.cs", 18, 15)] // 2 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInReturnTypeOnInterceptor, @"InterceptsLocation(""Program.cs"", 18, 15)").WithArguments("C.Method2()").WithLocation(27, 6) + ); + + comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }, options: WithNullableDisable()); + comp.VerifyEmitDiagnostics( + // Program.cs(6,31): warning CS8632: The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. + // public void Method1(string? param1) => throw null!; + Diagnostic(ErrorCode.WRN_MissingNonNullTypesContextForAnnotation, "?").WithLocation(6, 31), + // Program.cs(28,25): warning CS8632: The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. + // public static string? Interceptor2(this C s) => throw null!; + Diagnostic(ErrorCode.WRN_MissingNonNullTypesContextForAnnotation, "?").WithLocation(28, 25) + ); + } + + [Fact] + public void SignatureMismatch_06() + { + // 'dynamic' difference + var source = """ + using System.Runtime.CompilerServices; + + class C + { + [Interceptable] + public void Method1(object param1) => throw null!; + + [Interceptable] + public dynamic Method2() => throw null!; + } + + static class Program + { + public static void Main() + { + var c = new C(); + c.Method1("call site"); + _ = c.Method2(); + } + } + + static class D + { + [InterceptsLocation("Program.cs", 17, 11)] // 1 + public static void Interceptor1(this C s, dynamic param2) => throw null!; + + [InterceptsLocation("Program.cs", 18, 15)] // 2 + public static object Interceptor2(this C s) => throw null!; + } + """; + var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); + comp.VerifyEmitDiagnostics( + // Program.cs(24,6): warning CS27017: Intercepting a call to 'C.Method1(object)' with interceptor 'D.Interceptor1(C, dynamic)', but the signatures do not match. + // [InterceptsLocation("Program.cs", 17, 11)] // 1 + Diagnostic(ErrorCode.WRN_InterceptorSignatureMismatch, @"InterceptsLocation(""Program.cs"", 17, 11)").WithArguments("C.Method1(object)", "D.Interceptor1(C, dynamic)").WithLocation(24, 6), + // Program.cs(27,6): warning CS27017: Intercepting a call to 'C.Method2()' with interceptor 'D.Interceptor2(C)', but the signatures do not match. + // [InterceptsLocation("Program.cs", 18, 15)] // 2 + Diagnostic(ErrorCode.WRN_InterceptorSignatureMismatch, @"InterceptsLocation(""Program.cs"", 18, 15)").WithArguments("C.Method2()", "D.Interceptor2(C)").WithLocation(27, 6) + ); + } + + [Fact] + public void SignatureMismatch_07() + { + // tuple element name difference + var source = """ + using System.Runtime.CompilerServices; + using System; + + class C + { + [Interceptable] + public void Method1((string x, string y) param1) => throw null!; + } + + static class Program + { + public static void Main() + { + var c = new C(); + c.Method1(default!); + } + } + + static class D + { + [InterceptsLocation("Program.cs", 15, 11)] + public static void Interceptor1(this C s, (string a, string b) param2) => Console.Write(1); + } + """; + var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void ScopedMismatch_01() + { + // Unsafe 'scoped' difference + var source = """ + using System.Runtime.CompilerServices; + + class C + { + [Interceptable] + public static ref int InterceptableMethod(scoped ref int value) => throw null!; + } + + static class Program + { + public static void Main() + { + int i = 0; + C.InterceptableMethod(ref i); + } + } + + static class D + { + [InterceptsLocation("Program.cs", 14, 11)] // 1 + public static ref int Interceptor1(ref int value) => throw null!; + } + """; + var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }, options: WithNullableEnable()); + comp.VerifyEmitDiagnostics( + // Program.cs(20,6): error CS27019: Cannot intercept call to 'C.InterceptableMethod(scoped ref int)' with 'D.Interceptor1(ref int)' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + // [InterceptsLocation("Program.cs", 14, 11)] // 1 + Diagnostic(ErrorCode.ERR_InterceptorScopedMismatch, @"InterceptsLocation(""Program.cs"", 14, 11)").WithArguments("C.InterceptableMethod(scoped ref int)", "D.Interceptor1(ref int)").WithLocation(20, 6) + ); + } + + [Fact] + public void ScopedMismatch_02() + { + // safe 'scoped' difference + var source = """ + using System.Runtime.CompilerServices; + using System; + + class C + { + [Interceptable] + public static ref int InterceptableMethod(ref int value) => throw null!; + } + + static class Program + { + public static void Main() + { + int i = 0; + _ = C.InterceptableMethod(ref i); + } + } + + static class D + { + static int i; + + [InterceptsLocation("Program.cs", 15, 15)] + public static ref int Interceptor1(scoped ref int value) + { + Console.Write(1); + return ref i; + } + } + """; + var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void ScopedMismatch_03() + { + // safe '[UnscopedRef]' difference + var source = """ + using System.Diagnostics.CodeAnalysis; + using System.Runtime.CompilerServices; + using System; + + class C + { + [Interceptable] + public static ref int InterceptableMethod([UnscopedRef] out int value) => throw null!; + } + + static class Program + { + public static void Main() + { + _ = C.InterceptableMethod(out int i); + } + } + + static class D + { + static int i; + + [InterceptsLocation("Program.cs", 15, 15)] + public static ref int Interceptor1(out int value) + { + Console.Write(1); + value = 0; + return ref i; + } + } + """; + var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource, (UnscopedRefAttributeDefinition, "UnscopedRefAttribute.cs") }, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void ScopedMismatch_04() + { + // unsafe '[UnscopedRef]' difference + var source = """ + using System.Diagnostics.CodeAnalysis; + using System.Runtime.CompilerServices; + using System; + + class C + { + [Interceptable] + public static ref int InterceptableMethod(out int value) => throw null!; + } + + static class Program + { + public static void Main() + { + C.InterceptableMethod(out int i); + } + } + + static class D + { + [InterceptsLocation("Program.cs", 15, 11)] // 1 + public static ref int Interceptor1([UnscopedRef] out int value) => throw null!; + } + """; + var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource, (UnscopedRefAttributeDefinition, "UnscopedRefAttribute.cs") }, options: WithNullableEnable()); + comp.VerifyEmitDiagnostics( + // Program.cs(21,6): error CS27019: Cannot intercept call to 'C.InterceptableMethod(out int)' with 'D.Interceptor1(out int)' because of a difference in 'scoped' modifiers or '[UnscopedRef]' attributes. + // [InterceptsLocation("Program.cs", 15, 11)] // 1 + Diagnostic(ErrorCode.ERR_InterceptorScopedMismatch, @"InterceptsLocation(""Program.cs"", 15, 11)").WithArguments("C.InterceptableMethod(out int)", "D.Interceptor1(out int)").WithLocation(21, 6) + ); + } + + [Fact] + public void ReferenceEquals_01() + { + // A call to 'object.ReferenceEquals(a, b)' is defined as being equivalent to '(object)a == b'. + var source = """ + using System.Runtime.CompilerServices; + + static class D + { + [Interceptable] + public static bool Interceptable(object? obj1, object? obj2) => throw null!; + + public static void M0(object? obj1, object? obj2) + { + if (obj1 == obj2) + throw null!; + } + + public static void M1(object? obj1, object? obj2) + { + if (Interceptable(obj1, obj2)) + throw null!; + } + + public static void M2(object? obj1, object? obj2) + { + if (Interceptable(obj1, obj2)) + throw null!; + } + } + + namespace System + { + public class Object + { + [InterceptsLocation("Program.cs", 16, 13)] + public static bool ReferenceEquals(object? obj1, object? obj2) => throw null!; + + [InterceptsLocation("Program.cs", 22, 13)] + public static bool NotReferenceEquals(object? obj1, object? obj2) => throw null!; + } + + public class Void { } + public struct Boolean { } + public class String { } + public class Attribute { } + public abstract class Enum { } + public enum AttributeTargets { } + public class AttributeUsageAttribute : Attribute + { + public AttributeUsageAttribute(AttributeTargets targets) { } + public bool AllowMultiple { get; set; } + public bool Inherited { get; set; } + } + public class Exception { } + public abstract class ValueType { } + public struct Int32 { } + public struct Byte { } + } + + namespace System.Runtime.CompilerServices + { + public sealed class InterceptableAttribute : Attribute { } + + public sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } + } + """; + var verifier = CompileAndVerify(CreateEmptyCompilation((source, "Program.cs"), options: WithNullableEnable()), verify: Verification.Skipped); + verifier.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).Verify(); + + var referenceEqualsCallIL = """ + { + // Code size 7 (0x7) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: bne.un.s IL_0006 + IL_0004: ldnull + IL_0005: throw + IL_0006: ret + } + """; + verifier.VerifyIL("D.M0", referenceEqualsCallIL); + verifier.VerifyIL("D.M1", referenceEqualsCallIL); + + verifier.VerifyIL("D.M2", """ + { + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "bool object.NotReferenceEquals(object, object)" + IL_0007: brfalse.s IL_000b + IL_0009: ldnull + IL_000a: throw + IL_000b: ret + } + """); + } + + [Fact] + public void ReferenceEquals_02() + { + // Intercept a call to object.ReferenceEquals + var source = """ + using System.Runtime.CompilerServices; + + static class D + { + public static void M0(object? obj1, object? obj2) + { + if (object.ReferenceEquals(obj1, obj2)) + throw null!; + } + + [InterceptsLocation("Program.cs", 7, 20)] + public static bool Interceptor(object? obj1, object? obj2) + { + return false; + } + } + + namespace System + { + public class Object + { + [Interceptable] + public static bool ReferenceEquals(object? obj1, object? obj2) => throw null!; + } + + public class Void { } + public struct Boolean { } + public class String { } + public class Attribute { } + public abstract class Enum { } + public enum AttributeTargets { } + public class AttributeUsageAttribute : Attribute + { + public AttributeUsageAttribute(AttributeTargets targets) { } + public bool AllowMultiple { get; set; } + public bool Inherited { get; set; } + } + public class Exception { } + public abstract class ValueType { } + public struct Int32 { } + public struct Byte { } + } + + namespace System.Runtime.CompilerServices + { + public sealed class InterceptableAttribute : Attribute { } + + public sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } + } + """; + var verifier = CompileAndVerify(CreateEmptyCompilation((source, "Program.cs"), options: WithNullableEnable()), verify: Verification.Skipped); + verifier.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).Verify(); + + verifier.VerifyIL("D.M0", """ + { + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "bool D.Interceptor(object, object)" + IL_0007: brfalse.s IL_000b + IL_0009: ldnull + IL_000a: throw + IL_000b: ret + } + """); + } + + [Fact] + public void ParamsMismatch_01() + { + // Test when interceptable method has 'params' parameter. + var source = """ + using System.Runtime.CompilerServices; + using System; + + class C + { + [Interceptable] + public static void InterceptableMethod(params int[] value) => throw null!; + } + + static class Program + { + public static void Main() + { + C.InterceptableMethod(1, 2, 3); + C.InterceptableMethod(4, 5, 6); + } + } + + static class D + { + [InterceptsLocation("Program.cs", 14, 11)] + public static void Interceptor1(int[] value) + { + foreach (var i in value) + Console.Write(i); + } + + [InterceptsLocation("Program.cs", 15, 11)] + public static void Interceptor2(params int[] value) + { + foreach (var i in value) + Console.Write(i); + } + } + """; + var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "123456"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void ParamsMismatch_02() + { + // Test when interceptable method lacks 'params' parameter, and interceptor has one, and method is called as if it has one. + var source = """ + using System.Runtime.CompilerServices; + using System; + + class C + { + [Interceptable] + public static void InterceptableMethod(int[] value) => throw null!; + } + + static class Program + { + public static void Main() + { + C.InterceptableMethod(1, 2, 3 ); // 1 + C.InterceptableMethod(4, 5, 6); // 2 + } + } + + static class D + { + [InterceptsLocation("Program.cs", 14, 11)] + public static void Interceptor1(int[] value) + { + foreach (var i in value) + Console.Write(i); + } + + [InterceptsLocation("Program.cs", 15, 11)] + public static void Interceptor2(params int[] value) + { + foreach (var i in value) + Console.Write(i); + } + } + """; + var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); + comp.VerifyEmitDiagnostics( + // Program.cs(14,11): error CS1501: No overload for method 'InterceptableMethod' takes 3 arguments + // C.InterceptableMethod(1, 2, 3 ); // 1 + Diagnostic(ErrorCode.ERR_BadArgCount, "InterceptableMethod").WithArguments("InterceptableMethod", "3").WithLocation(14, 11), + // Program.cs(15,11): error CS1501: No overload for method 'InterceptableMethod' takes 3 arguments + // C.InterceptableMethod(4, 5, 6); // 2 + Diagnostic(ErrorCode.ERR_BadArgCount, "InterceptableMethod").WithArguments("InterceptableMethod", "3").WithLocation(15, 11)); + } + + [Fact] + public void ParamsMismatch_03() + { + // Test when interceptable method lacks 'params' parameter, and interceptor has one, and method is called in normal form. + var source = """ + using System.Runtime.CompilerServices; + using System; + + class C + { + [Interceptable] + public static void InterceptableMethod(int[] value) => throw null!; + } + + static class Program + { + public static void Main() + { + C.InterceptableMethod(new[] { 1, 2, 3 }); + C.InterceptableMethod(new[] { 4, 5, 6 }); + } + } + + static class D + { + [InterceptsLocation("Program.cs", 14, 11)] + public static void Interceptor1(int[] value) + { + foreach (var i in value) + Console.Write(i); + } + + [InterceptsLocation("Program.cs", 15, 11)] + public static void Interceptor2(params int[] value) + { + foreach (var i in value) + Console.Write(i); + } + } + """; + var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "123456"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void InterpolatedStringHandler_01() + { + // Verify that interpolated string-related attributes on an intercepted call use the attributes from the interceptable method. + var code = """ +using System; using System.Runtime.CompilerServices; var s = new S1(); @@ -1712,8 +2957,192 @@ class D """; var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); comp.VerifyEmitDiagnostics( - // OtherFile.cs(48,6): error CS27002: Cannot intercept: compilation does not contain a file with path 'OtherFile.cs'. + // OtherFile.cs(48,25): error CS27002: Cannot intercept: compilation does not contain a file with path 'OtherFile.cs'. // [InterceptsLocation("OtherFile.cs", 42, 9)] - Diagnostic(ErrorCode.ERR_InterceptorPathNotInCompilation, @"InterceptsLocation(""OtherFile.cs"", 42, 9)").WithArguments("OtherFile.cs").WithLocation(48, 6)); + Diagnostic(ErrorCode.ERR_InterceptorPathNotInCompilation, @"""OtherFile.cs""").WithArguments("OtherFile.cs").WithLocation(48, 25)); + } + + [Fact] + public void ObsoleteInterceptor() + { + // Expect no Obsolete diagnostics to be reported + var source = """ + using System.Runtime.CompilerServices; + using System; + + C.M(); + + class C + { + [Interceptable] + public static void M() => throw null!; + } + + class D + { + [Obsolete] + [InterceptsLocation("Program.cs", 4, 3)] + public static void M1() => Console.Write(1); + } + """; + + var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void CallerInfo() + { + // CallerLineNumber, etc. on the interceptor doesn't affect the default arguments passed to an intercepted call. + var source = """ + using System.Runtime.CompilerServices; + using System; + + C.M(); + + class C + { + [Interceptable] + public static void M(int lineNumber = 1) => throw null!; + } + + class D + { + [InterceptsLocation("Program.cs", 4, 3)] + public static void M1([CallerLineNumber] int lineNumber = 0) => Console.Write(lineNumber); + } + """; + + var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void InterceptableExplicitImplementation() + { + var source = """ + using System.Runtime.CompilerServices; + using System; + + var c = new C(); + ((I)c).M(); + + interface I + { + void M(); + } + + class C : I + { + [Interceptable] // 1 + void I.M() => throw null!; + } + + static class D + { + [InterceptsLocation("Program.cs", 5, 8)] + public static void Interceptor(this I i) => throw null!; + } + """; + + var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }); + comp.VerifyEmitDiagnostics( + // Program.cs(14,6): error CS27008: An interceptable method must be an ordinary member method. + // [Interceptable] // 1 + Diagnostic(ErrorCode.ERR_InterceptableMethodMustBeOrdinary, "Interceptable").WithLocation(14, 6) + ); + } + + [Fact] + public void InterceptorExtern() + { + var source = """ + using System.Runtime.CompilerServices; + + C.M(); + + class C + { + [Interceptable] + public static void M() => throw null!; + } + + static class D + { + [InterceptsLocation("Program.cs", 3, 3)] + public static extern void Interceptor(); + } + """; + + var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("", """ + { + // Code size 6 (0x6) + .maxstack 0 + IL_0000: call "void D.Interceptor()" + IL_0005: ret + } + """); + } + + [Fact] + public void InterceptorAbstract() + { + var source = """ + using System.Runtime.CompilerServices; + using System; + + var d = new D(); + d.M(); + + abstract class C + { + [Interceptable] + public void M() => throw null!; + + [InterceptsLocation("Program.cs", 5, 3)] + public abstract void Interceptor(); + } + + class D : C + { + public override void Interceptor() => Console.Write(1); + } + """; + + var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void InterceptorInterface() + { + var source = """ + using System.Runtime.CompilerServices; + using System; + + I i = new C(); + i.M(); + + interface I + { + [Interceptable] + public void M(); + + [InterceptsLocation("Program.cs", 5, 3)] + void Interceptor(); + } + + class C : I + { + public void M() => throw null!; + public void Interceptor() => Console.Write(1); + } + """; + + var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "1"); + verifier.VerifyDiagnostics(); } } diff --git a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs index dba8f95929a5b..b4388fa679963 100644 --- a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs +++ b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs @@ -305,6 +305,9 @@ public void WarningLevel_2() case ErrorCode.WRN_CapturedPrimaryConstructorParameterPassedToBase: case ErrorCode.WRN_UnreadPrimaryConstructorParameter: case ErrorCode.WRN_CallNotInterceptable: + case ErrorCode.WRN_InterceptorSignatureMismatch: + case ErrorCode.WRN_NullabilityMismatchInReturnTypeOnInterceptor: + case ErrorCode.WRN_NullabilityMismatchInParameterTypeOnInterceptor: Assert.Equal(1, ErrorFacts.GetWarningLevel(errorCode)); break; case ErrorCode.WRN_MainIgnored: @@ -2942,6 +2945,12 @@ public void TestIsBuildOnlyDiagnostic() case ErrorCode.ERR_InterceptorSignatureMismatch: case ErrorCode.ERR_InterceptorMustHaveMatchingThisParameter: case ErrorCode.ERR_InterceptorMustNotHaveThisParameter: + case ErrorCode.ERR_DuplicateInterceptor: + case ErrorCode.WRN_InterceptorSignatureMismatch: + case ErrorCode.ERR_InterceptorNotAccessible: + case ErrorCode.ERR_InterceptorScopedMismatch: + case ErrorCode.WRN_NullabilityMismatchInReturnTypeOnInterceptor: + case ErrorCode.WRN_NullabilityMismatchInParameterTypeOnInterceptor: Assert.True(isBuildOnly, $"Check failed for ErrorCode.{errorCode}"); break; diff --git a/src/Compilers/Core/Portable/InternalUtilities/ConcurrentDictionaryExtensions.cs b/src/Compilers/Core/Portable/InternalUtilities/ConcurrentDictionaryExtensions.cs index 061b7394c93f3..900003e99ea6d 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/ConcurrentDictionaryExtensions.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/ConcurrentDictionaryExtensions.cs @@ -38,5 +38,25 @@ public static TValue GetOrAdd(this ConcurrentDictionary.AddOrUpdate(TKey key, Func addValueFactory, Func updateValueFactory, TArg factoryArgument); + public static TValue AddOrUpdate( + this ConcurrentDictionary dictionary, + TKey key, + Func addValueFactory, + Func updateValueFactory, + TArg factoryArgument) + where TKey : notnull + { +#if NETCOREAPP + return dictionary.AddOrUpdate(key, addValueFactory, updateValueFactory, factoryArgument); +#else + using var _a = PooledDelegates.GetPooledFunction(addValueFactory, factoryArgument, out var pooledAddValueFactory); + using var _b = PooledDelegates.GetPooledFunction(updateValueFactory, factoryArgument, out var pooledUpdateValueFactory); + return dictionary.AddOrUpdate(key, pooledAddValueFactory, pooledUpdateValueFactory); +#endif + } + } } diff --git a/src/EditorFeatures/CSharp/LanguageServer/CSharpLspBuildOnlyDiagnostics.cs b/src/EditorFeatures/CSharp/LanguageServer/CSharpLspBuildOnlyDiagnostics.cs index 30dcbe4de81ea..48cd732ab48dc 100644 --- a/src/EditorFeatures/CSharp/LanguageServer/CSharpLspBuildOnlyDiagnostics.cs +++ b/src/EditorFeatures/CSharp/LanguageServer/CSharpLspBuildOnlyDiagnostics.cs @@ -47,7 +47,13 @@ namespace Microsoft.CodeAnalysis.CSharp.LanguageServer "CS27000", // ErrorCode.WRN_CallNotInterceptable: "CS27007", // ErrorCode.ERR_InterceptorSignatureMismatch "CS27011", // ErrorCode.ERR_InterceptorMustHaveMatchingThisParameter - "CS27012" // ErrorCode.ERR_InterceptorMustNotHaveThisParameter + "CS27012", // ErrorCode.ERR_InterceptorMustNotHaveThisParameter + "CS27016", // ErrorCode.ERR_DuplicateInterceptor + "CS27017", // ErrorCode.WRN_InterceptorSignatureMismatch, + "CS27018", // ErrorCode.ERR_InterceptorNotAccessible + "CS27019", // ErrorCode.ERR_InterceptorScopedMismatch + "CS27021", // ErrorCode.WRN_NullabilityMismatchInReturnTypeOnInterceptor + "CS27022" // ErrorCode.WRN_NullabilityMismatchInParameterTypeOnInterceptor )] internal sealed class CSharpLspBuildOnlyDiagnostics : ILspBuildOnlyDiagnostics {