From ae2c98764fafd9e64c331a24e1351a6d37ce4245 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Mon, 8 Apr 2019 16:55:32 -0700 Subject: [PATCH 1/7] Implement design changes for "pattern" Index/Range indexers Implements most of the design changes specified in https://github.com/dotnet/csharplang/blob/c229cae634bd59a6a13b9ed464a4cab782a95e5d/proposals/index-range-changes.md This PR focuses on getting the simple end-to-end scenario working, not focusing entirely on codegen quality. I expect to follow-up later with the optimizations mentioned about eliminating use of the Index/Range helpers entirely if they can be elided. --- .../Portable/Binder/Binder_Expressions.cs | 274 +++++++++++- .../CSharp/Portable/BoundTree/BoundNodes.xml | 9 + .../CSharp/Portable/BoundTree/Expression.cs | 5 + .../Portable/FlowAnalysis/AbstractFlowPass.cs | 16 + .../Portable/FlowAnalysis/NullableWalker.cs | 14 + .../Generated/BoundNodes.xml.Generated.cs | 75 ++++ .../LocalRewriter_IndexerAccess.cs | 106 ++++- .../Lowering/SyntheticBoundNodeFactory.cs | 5 + .../Operations/CSharpOperationFactory.cs | 1 + .../Test/Emit/CodeGen/IndexAndRangeTests.cs | 397 ++++++++++++++++-- ..._IFromEndIndexOperation_IRangeOperation.cs | 96 +++++ .../Semantic/Semantics/IndexAndRangeTests.cs | 186 ++++++-- .../Semantics/NullableReferenceTypesTests.cs | 5 +- .../Compilation/SemanticModelAPITests.cs | 39 ++ .../Core/Portable/PublicAPI.Unshipped.txt | 3 + .../Portable/Symbols/WellKnownMemberNames.cs | 15 + .../Core/Portable/WellKnownMember.cs | 3 + .../Core/Portable/WellKnownMembers.cs | 26 ++ .../Test/Utilities/CSharp/TestSources.cs | 35 -- 19 files changed, 1180 insertions(+), 130 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index c6227bef11aed..db9c3873c84cb 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -7188,7 +7188,18 @@ private BoundExpression BindIndexerAccess(ExpressionSyntax node, BoundExpression if (!lookupResult.IsMultiViable) { - indexerAccessExpression = BadIndexerExpression(node, expr, analyzedArguments, lookupResult.Error, diagnostics); + if (TryBindIndexOrRangeIndexer( + node, + expr, + analyzedArguments.Arguments, + out var patternIndexerAccess)) + { + indexerAccessExpression = patternIndexerAccess; + } + else + { + indexerAccessExpression = BadIndexerExpression(node, expr, analyzedArguments, lookupResult.Error, diagnostics); + } } else { @@ -7350,22 +7361,33 @@ private BoundExpression BindIndexedPropertyAccess(SyntaxNode syntax, BoundExpres if (!analyzedArguments.HasErrors) { - // Dev10 uses the "this" keyword as the method name for indexers. - var candidate = candidates[0]; - var name = candidate.IsIndexer ? SyntaxFacts.GetText(SyntaxKind.ThisKeyword) : candidate.Name; - - overloadResolutionResult.ReportDiagnostics( - binder: this, - location: syntax.Location, - nodeOpt: syntax, - diagnostics: diagnostics, - name: name, - receiver: null, - invokedExpression: null, - arguments: analyzedArguments, - memberGroup: candidates, - typeContainingConstructor: null, - delegateTypeBeingInvoked: null); + if (TryBindIndexOrRangeIndexer( + syntax, + receiverOpt, + analyzedArguments.Arguments, + out var patternIndexerAccess)) + { + return patternIndexerAccess; + } + else + { + // Dev10 uses the "this" keyword as the method name for indexers. + var candidate = candidates[0]; + var name = candidate.IsIndexer ? SyntaxFacts.GetText(SyntaxKind.ThisKeyword) : candidate.Name; + + overloadResolutionResult.ReportDiagnostics( + binder: this, + location: syntax.Location, + nodeOpt: syntax, + diagnostics: diagnostics, + name: name, + receiver: null, + invokedExpression: null, + arguments: analyzedArguments, + memberGroup: candidates, + typeContainingConstructor: null, + delegateTypeBeingInvoked: null); + } } ImmutableArray arguments = BuildArgumentsForErrorRecovery(analyzedArguments, candidates); @@ -7438,6 +7460,224 @@ private BoundExpression BindIndexedPropertyAccess(SyntaxNode syntax, BoundExpres return propertyAccess; } + private bool TryBindIndexOrRangeIndexer( + SyntaxNode syntax, + BoundExpression receiverOpt, + ArrayBuilder arguments, + out BoundIndexOrRangePatternIndexerAccess patternIndexerAccess) + { + // Verify a few things up-front, namely that we have a single argument + // to this indexer that has an Index or Range type and that there is + // a real receiver with a known type + + if (arguments.Count != 1) + { + patternIndexerAccess = null; + return false; + } + + var argType = arguments[0].Type; + bool argIsIndex = TypeSymbol.Equals(argType, + Compilation.GetWellKnownType(WellKnownType.System_Index), + TypeCompareKind.ConsiderEverything); + bool argIsRange = !argIsIndex || TypeSymbol.Equals(argType, + Compilation.GetWellKnownType(WellKnownType.System_Range), + TypeCompareKind.ConsiderEverything); + + if ((!argIsIndex && !argIsRange) || + !(receiverOpt?.Type is TypeSymbol receiverType)) + { + patternIndexerAccess = null; + return false; + } + + // SPEC: + + // An indexer invocation with a single argument of System.Index or System.Range will + // succeed if the receiver type conforms to an appropriate pattern, namely + + // 1. The receiver type's original definition has an accessible property getter that returns + // an int and has the name Length or Count + // 2. For Index: Has an accessible indexer with a single int parameter + // For Range: Has an accessible Slice method that takes two int parameters + + PropertySymbol lengthOrCountProperty; + + var lookupResult = LookupResult.GetInstance(); + HashSet useSiteDiagnostics = null; + + // look for Length first + LookupMembersInType( + lookupResult, + receiverType, + WellKnownMemberNames.LengthPropertyName, + arity: 0, + basesBeingResolved: null, + LookupOptions.Default, + originalBinder: this, + diagnose: false, + useSiteDiagnostics: ref useSiteDiagnostics); + + if (!lookupSatisfiesLengthPattern(out lengthOrCountProperty)) + { + // Look for Count second + lookupResult.Clear(); + useSiteDiagnostics?.Clear(); + LookupMembersInType( + lookupResult, + receiverType, + WellKnownMemberNames.CountPropertyName, + arity: 0, + basesBeingResolved: null, + LookupOptions.Default, + originalBinder: this, + diagnose: false, + ref useSiteDiagnostics); + + if (!lookupSatisfiesLengthPattern(out lengthOrCountProperty)) + { + cleanup(); + patternIndexerAccess = null; + return false; + } + } + + Debug.Assert(lengthOrCountProperty is { }); + lookupResult.Clear(); + useSiteDiagnostics?.Clear(); + + if (argIsIndex) + { + // Look for `T this[int i]` indexer + + LookupMembersInType( + lookupResult, + receiverType, + WellKnownMemberNames.Indexer, + arity: 0, + basesBeingResolved: null, + LookupOptions.Default, + originalBinder: this, + diagnose: false, + ref useSiteDiagnostics); + + if (lookupResult.IsMultiViable) + { + foreach (var candidate in lookupResult.Symbols) + { + if (!candidate.IsStatic && + IsAccessible(candidate, ref useSiteDiagnostics) && + candidate is PropertySymbol property && + property.GetOwnOrInheritedGetMethod() is MethodSymbol getMethod && + getMethod.OriginalDefinition is var original && + original.ParameterCount == 1 && + isIntNotByRef(original.Parameters[0])) + { + patternIndexerAccess = new BoundIndexOrRangePatternIndexerAccess( + syntax, + receiverOpt, + lengthOrCountProperty, + getMethod, + arguments[0], + getMethod.ReturnType); + cleanup(); + return true; + } + } + } + } + else if (receiverType.SpecialType == SpecialType.System_String) + { + Debug.Assert(argIsRange); + // Look for Substring + var substring = (MethodSymbol)Compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Substring); + if (substring is { }) + { + patternIndexerAccess = new BoundIndexOrRangePatternIndexerAccess( + syntax, + receiverOpt, + lengthOrCountProperty, + substring, + arguments[0], + substring.ReturnType); + cleanup(); + return true; + } + } + else + { + Debug.Assert(argIsRange); + // Look for `T Slice(int, int)` indexer + + LookupMembersInType( + lookupResult, + receiverType, + WellKnownMemberNames.SliceMethodName, + arity: 0, + basesBeingResolved: null, + LookupOptions.Default, + originalBinder: this, + diagnose: false, + ref useSiteDiagnostics); + + if (lookupResult.IsMultiViable) + { + foreach (var candidate in lookupResult.Symbols) + { + if (!candidate.IsStatic && + IsAccessible(candidate, ref useSiteDiagnostics) && + candidate is MethodSymbol method && + method.OriginalDefinition is var original && + original.ParameterCount == 2 && + isIntNotByRef(original.Parameters[0]) && + isIntNotByRef(original.Parameters[1])) + { + patternIndexerAccess = new BoundIndexOrRangePatternIndexerAccess( + syntax, + receiverOpt, + lengthOrCountProperty, + method, + arguments[0], + method.ReturnType); + cleanup(); + return true; + } + } + } + } + + cleanup(); + patternIndexerAccess = null; + return false; + + bool isIntNotByRef(ParameterSymbol param) + => param.Type.SpecialType == SpecialType.System_Int32 && + param.RefKind == RefKind.None; + + void cleanup() + { + lookupResult.Free(); + useSiteDiagnostics = null; + } + + bool lookupSatisfiesLengthPattern(out PropertySymbol valid) + { + if (lookupResult.IsSingleViable && + lookupResult.SingleSymbolOrDefault is PropertySymbol property && + property.GetOwnOrInheritedGetMethod().OriginalDefinition is MethodSymbol getMethod && + getMethod.ReturnType.SpecialType == SpecialType.System_Int32 && + getMethod.RefKind == RefKind.None && + !getMethod.IsStatic && + IsAccessible(getMethod, ref useSiteDiagnostics)) + { + valid = property; + return true; + } + valid = null; + return false; + } + } + private ErrorPropertySymbol CreateErrorPropertySymbol(ImmutableArray propertyGroup) { TypeSymbol propertyType = GetCommonTypeOrReturnType(propertyGroup) ?? CreateErrorType(); diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index d7e1b552d6fca..fab7619e9a363 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -1744,6 +1744,15 @@ + + + + + + + + + diff --git a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs index 46ab076614d60..7243fcf1a0c90 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs @@ -169,4 +169,9 @@ internal partial class BoundPassByCopy { protected override ImmutableArray Children => ImmutableArray.Create(this.Expression); } + + internal partial class BoundIndexOrRangePatternIndexerAccess + { + protected override ImmutableArray Children => ImmutableArray.Create(Receiver, Argument); + } } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index 5158f7203b7a2..6763aa8c31150 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -1188,6 +1188,22 @@ public override BoundNode VisitIndexerAccess(BoundIndexerAccess node) return null; } + public override BoundNode VisitIndexOrRangePatternIndexerAccess(BoundIndexOrRangePatternIndexerAccess node) + { + // Index or Range pattern indexers evaluate the following in order: + // 1. The receiver + // 1. The Count or Length method off the receiver + // 2. The argument to the access + // 3. The pattern method + VisitRvalue(node.Receiver); + var method = GetReadMethod(node.LengthOrCountProperty); + VisitReceiverAfterCall(node.Receiver, method); + VisitRvalue(node.Argument); + VisitReceiverAfterCall(node.Receiver, node.PatternMethod); + + return null; + } + public override BoundNode VisitEventAssignmentOperator(BoundEventAssignmentOperator node) { VisitRvalue(node.ReceiverOpt); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 3fc924edee863..7b7fbe28b3562 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4935,6 +4935,20 @@ public override BoundNode VisitIndexerAccess(BoundIndexerAccess node) return null; } + public override BoundNode VisitIndexOrRangePatternIndexerAccess(BoundIndexOrRangePatternIndexerAccess node) + { + var receiverType = VisitRvalueWithState(node.Receiver); + VisitRvalue(node.Argument); + var patternMethod = node.PatternMethod; + if (!receiverType.HasNullType) + { + patternMethod = (MethodSymbol)AsMemberOfType(receiverType.Type, patternMethod); + } + + LvalueResultType = patternMethod.ReturnTypeWithAnnotations; + return null; + } + public override BoundNode VisitEventAccess(BoundEventAccess node) { VisitMemberAccess(node, node.ReceiverOpt, node.EventSymbol); diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index 575d2beb29744..606964db3e740 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -178,6 +178,7 @@ internal enum BoundKind: byte PropertyAccess, EventAccess, IndexerAccess, + IndexOrRangePatternIndexerAccess, DynamicIndexerAccess, Lambda, UnboundLambda, @@ -6673,6 +6674,53 @@ protected override BoundExpression ShallowClone() } } + internal sealed partial class BoundIndexOrRangePatternIndexerAccess : BoundExpression + { + public BoundIndexOrRangePatternIndexerAccess(SyntaxNode syntax, BoundExpression receiver, PropertySymbol lengthOrCountProperty, MethodSymbol patternMethod, BoundExpression argument, TypeSymbol type, bool hasErrors = false) + : base(BoundKind.IndexOrRangePatternIndexerAccess, syntax, type, hasErrors || receiver.HasErrors() || argument.HasErrors()) + { + + Debug.Assert((object)receiver != null, "Field 'receiver' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + Debug.Assert((object)lengthOrCountProperty != null, "Field 'lengthOrCountProperty' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + Debug.Assert((object)patternMethod != null, "Field 'patternMethod' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + Debug.Assert((object)argument != null, "Field 'argument' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + Debug.Assert((object)type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + + this.Receiver = receiver; + this.LengthOrCountProperty = lengthOrCountProperty; + this.PatternMethod = patternMethod; + this.Argument = argument; + } + + + public BoundExpression Receiver { get; } + + public PropertySymbol LengthOrCountProperty { get; } + + public MethodSymbol PatternMethod { get; } + + public BoundExpression Argument { get; } + public override BoundNode Accept(BoundTreeVisitor visitor) => visitor.VisitIndexOrRangePatternIndexerAccess(this); + + public BoundIndexOrRangePatternIndexerAccess Update(BoundExpression receiver, PropertySymbol lengthOrCountProperty, MethodSymbol patternMethod, BoundExpression argument, TypeSymbol type) + { + if (receiver != this.Receiver || lengthOrCountProperty != this.LengthOrCountProperty || patternMethod != this.PatternMethod || argument != this.Argument || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) + { + var result = new BoundIndexOrRangePatternIndexerAccess(this.Syntax, receiver, lengthOrCountProperty, patternMethod, argument, type, this.HasErrors); + result.CopyAttributes(this); + return result; + } + return this; + } + + protected override BoundExpression ShallowClone() + { + var result = new BoundIndexOrRangePatternIndexerAccess(this.Syntax, this.Receiver, this.LengthOrCountProperty, this.PatternMethod, this.Argument, this.Type, this.HasErrors); + result.CopyAttributes(this); + return result; + } + } + internal sealed partial class BoundDynamicIndexerAccess : BoundExpression { public BoundDynamicIndexerAccess(SyntaxNode syntax, BoundExpression receiverOpt, ImmutableArray arguments, ImmutableArray argumentNamesOpt, ImmutableArray argumentRefKindsOpt, ImmutableArray applicableIndexers, TypeSymbol type, bool hasErrors = false) @@ -7855,6 +7903,8 @@ internal R VisitInternal(BoundNode node, A arg) return VisitEventAccess(node as BoundEventAccess, arg); case BoundKind.IndexerAccess: return VisitIndexerAccess(node as BoundIndexerAccess, arg); + case BoundKind.IndexOrRangePatternIndexerAccess: + return VisitIndexOrRangePatternIndexerAccess(node as BoundIndexOrRangePatternIndexerAccess, arg); case BoundKind.DynamicIndexerAccess: return VisitDynamicIndexerAccess(node as BoundDynamicIndexerAccess, arg); case BoundKind.Lambda: @@ -8067,6 +8117,7 @@ internal abstract partial class BoundTreeVisitor public virtual R VisitPropertyAccess(BoundPropertyAccess node, A arg) => this.DefaultVisit(node, arg); public virtual R VisitEventAccess(BoundEventAccess node, A arg) => this.DefaultVisit(node, arg); public virtual R VisitIndexerAccess(BoundIndexerAccess node, A arg) => this.DefaultVisit(node, arg); + public virtual R VisitIndexOrRangePatternIndexerAccess(BoundIndexOrRangePatternIndexerAccess node, A arg) => this.DefaultVisit(node, arg); public virtual R VisitDynamicIndexerAccess(BoundDynamicIndexerAccess node, A arg) => this.DefaultVisit(node, arg); public virtual R VisitLambda(BoundLambda node, A arg) => this.DefaultVisit(node, arg); public virtual R VisitUnboundLambda(UnboundLambda node, A arg) => this.DefaultVisit(node, arg); @@ -8252,6 +8303,7 @@ internal abstract partial class BoundTreeVisitor public virtual BoundNode VisitPropertyAccess(BoundPropertyAccess node) => this.DefaultVisit(node); public virtual BoundNode VisitEventAccess(BoundEventAccess node) => this.DefaultVisit(node); public virtual BoundNode VisitIndexerAccess(BoundIndexerAccess node) => this.DefaultVisit(node); + public virtual BoundNode VisitIndexOrRangePatternIndexerAccess(BoundIndexOrRangePatternIndexerAccess node) => this.DefaultVisit(node); public virtual BoundNode VisitDynamicIndexerAccess(BoundDynamicIndexerAccess node) => this.DefaultVisit(node); public virtual BoundNode VisitLambda(BoundLambda node) => this.DefaultVisit(node); public virtual BoundNode VisitUnboundLambda(UnboundLambda node) => this.DefaultVisit(node); @@ -8992,6 +9044,12 @@ public override BoundNode VisitIndexerAccess(BoundIndexerAccess node) this.VisitList(node.Arguments); return null; } + public override BoundNode VisitIndexOrRangePatternIndexerAccess(BoundIndexOrRangePatternIndexerAccess node) + { + this.Visit(node.Receiver); + this.Visit(node.Argument); + return null; + } public override BoundNode VisitDynamicIndexerAccess(BoundDynamicIndexerAccess node) { this.Visit(node.ReceiverOpt); @@ -10022,6 +10080,13 @@ public override BoundNode VisitIndexerAccess(BoundIndexerAccess node) TypeSymbol type = this.VisitType(node.Type); return node.Update(receiverOpt, node.Indexer, arguments, node.ArgumentNamesOpt, node.ArgumentRefKindsOpt, node.Expanded, node.ArgsToParamsOpt, node.BinderOpt, node.UseSetterForDefaultArgumentGeneration, type); } + public override BoundNode VisitIndexOrRangePatternIndexerAccess(BoundIndexOrRangePatternIndexerAccess node) + { + BoundExpression receiver = (BoundExpression)this.Visit(node.Receiver); + BoundExpression argument = (BoundExpression)this.Visit(node.Argument); + TypeSymbol type = this.VisitType(node.Type); + return node.Update(receiver, node.LengthOrCountProperty, node.PatternMethod, argument, type); + } public override BoundNode VisitDynamicIndexerAccess(BoundDynamicIndexerAccess node) { BoundExpression receiverOpt = (BoundExpression)this.Visit(node.ReceiverOpt); @@ -11448,6 +11513,16 @@ public override TreeDumperNode VisitYieldBreakStatement(BoundYieldBreakStatement new TreeDumperNode("isSuppressed", node.IsSuppressed, null) } ); + public override TreeDumperNode VisitIndexOrRangePatternIndexerAccess(BoundIndexOrRangePatternIndexerAccess node, object arg) => new TreeDumperNode("indexOrRangePatternIndexerAccess", null, new TreeDumperNode[] + { + new TreeDumperNode("receiver", null, new TreeDumperNode[] { Visit(node.Receiver, null) }), + new TreeDumperNode("lengthOrCountProperty", node.LengthOrCountProperty, null), + new TreeDumperNode("patternMethod", node.PatternMethod, null), + new TreeDumperNode("argument", null, new TreeDumperNode[] { Visit(node.Argument, null) }), + new TreeDumperNode("type", node.Type, null), + new TreeDumperNode("isSuppressed", node.IsSuppressed, null) + } + ); public override TreeDumperNode VisitDynamicIndexerAccess(BoundDynamicIndexerAccess node, object arg) => new TreeDumperNode("dynamicIndexerAccess", null, new TreeDumperNode[] { new TreeDumperNode("receiverOpt", null, new TreeDumperNode[] { Visit(node.ReceiverOpt, null) }), diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs index 2637f13ac03c5..c654a4dcb4201 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; @@ -69,7 +70,6 @@ public override BoundNode VisitIndexerAccess(BoundIndexerAccess node) return VisitIndexerAccess(node, isLeftOfAssignment: false); } - private BoundExpression VisitIndexerAccess(BoundIndexerAccess node, bool isLeftOfAssignment) { PropertySymbol indexer = node.Indexer; @@ -155,5 +155,109 @@ private BoundExpression VisitIndexerAccess(BoundIndexerAccess node, bool isLeftO } } } + + public override BoundNode VisitIndexOrRangePatternIndexerAccess(BoundIndexOrRangePatternIndexerAccess node) + { + if (TypeSymbol.Equals( + node.Argument.Type, + _compilation.GetWellKnownType(WellKnownType.System_Index), + TypeCompareKind.ConsiderEverything)) + { + return VisitIndexPatternIndexerAccess( + node.Receiver, + node.LengthOrCountProperty, + node.PatternMethod, + node.Argument); + } + else + { + Debug.Assert(TypeSymbol.Equals( + node.Argument.Type, + _compilation.GetWellKnownType(WellKnownType.System_Range), + TypeCompareKind.ConsiderEverything)); + return VisitRangePatternIndexerAccess( + node.Receiver, + node.LengthOrCountProperty, + node.PatternMethod, + node.Argument); + } + } + + private BoundExpression VisitIndexPatternIndexerAccess( + BoundExpression receiver, + PropertySymbol lengthOrCountProperty, + MethodSymbol intIndexerGetter, + BoundExpression argument) + { + // Lowered code: + // var receiver = receiverExpr; + // int length = receiver.length; + // int index = argument.GetOffset(length); + // receiver.get_Item(index); + + var F = _factory; + + var receiverLocal = F.StoreToTemp(VisitExpression(receiver), out var receiverStore); + var lengthLocal = F.StoreToTemp(F.Property(receiverLocal, lengthOrCountProperty), out var lengthStore); + var indexLocal = F.StoreToTemp( + F.Call( + VisitExpression(argument), + WellKnownMember.System_Index__GetOffset, + lengthLocal), + out var indexStore); + + return F.Sequence( + ImmutableArray.Create(receiverLocal.LocalSymbol, lengthLocal.LocalSymbol, indexLocal.LocalSymbol), + ImmutableArray.Create(receiverStore, lengthStore, indexStore), + F.Call(receiverLocal, intIndexerGetter, indexLocal)); + } + + private BoundExpression VisitRangePatternIndexerAccess( + BoundExpression receiver, + PropertySymbol lengthOrCountProperty, + MethodSymbol sliceMethod, + BoundExpression rangeArg) + { + // Lowered code: + // var receiver = receiverExpr; + // int length = receiver.length; + // Range range = argumentExpr; + // int start = range.Start.GetOffset(length) + // int end = range.End.GetOffset(length) + // receiver.Slice(start, end - start) + + var F = _factory; + + var receiverLocal = F.StoreToTemp(VisitExpression(receiver), out var receiverStore); + var lengthLocal = F.StoreToTemp(F.Property(receiverLocal, lengthOrCountProperty), out var lengthStore); + var rangeLocal = F.StoreToTemp(VisitExpression(rangeArg), out var rangeStore); + var startLocal = F.StoreToTemp( + F.Call( + F.Call(rangeLocal, F.WellKnownMethod(WellKnownMember.System_Range__get_Start)), + F.WellKnownMethod(WellKnownMember.System_Index__GetOffset), + lengthLocal), + out var startStore); + var endLocal = F.StoreToTemp( + F.Call( + F.Call(rangeLocal, F.WellKnownMethod(WellKnownMember.System_Range__get_End)), + F.WellKnownMethod(WellKnownMember.System_Index__GetOffset), + lengthLocal), + out var endStore); + + return F.Sequence( + ImmutableArray.Create( + receiverLocal.LocalSymbol, + lengthLocal.LocalSymbol, + rangeLocal.LocalSymbol, + startLocal.LocalSymbol, + endLocal.LocalSymbol), + ImmutableArray.Create( + receiverStore, + lengthStore, + rangeStore, + startStore, + endStore), + F.Call(receiverLocal, sliceMethod, startLocal, F.IntSubtract(endLocal, startLocal))); + } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index a1ab981af9b47..a9ead6912d5c9 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -572,6 +572,11 @@ public BoundBinaryOperator IntGreaterThanOrEqual(BoundExpression left, BoundExpr return Binary(BinaryOperatorKind.IntGreaterThanOrEqual, SpecialType(CodeAnalysis.SpecialType.System_Boolean), left, right); } + public BoundBinaryOperator IntSubtract(BoundExpression left, BoundExpression right) + { + return Binary(BinaryOperatorKind.IntSubtraction, SpecialType(CodeAnalysis.SpecialType.System_Int32), left, right); + } + public BoundLiteral Literal(int value) { return new BoundLiteral(Syntax, ConstantValue.Create(value), SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Int32)) { WasCompilerGenerated = true }; diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs index f8e3178354f8a..dc76d757af47e 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs @@ -304,6 +304,7 @@ internal IOperation CreateInternal(BoundNode boundNode) case BoundKind.StackAllocArrayCreation: case BoundKind.TypeExpression: case BoundKind.TypeOrValueExpression: + case BoundKind.IndexOrRangePatternIndexerAccess: Optional constantValue = ConvertToOptional((boundNode as BoundExpression)?.ConstantValue); bool isImplicit = boundNode.WasCompilerGenerated; diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs index 602dc1e322f5b..2cb18ac113dba 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs @@ -16,6 +16,267 @@ private CompilationVerifier CompileAndVerifyWithIndexAndRange(string s, string e return CompileAndVerify(comp, expectedOutput: expectedOutput); } + [Fact] + public void PatternIndexList() + { + var src = @" +using System; +using System.Collections.Generic; +class C +{ + static void Main() + { + var list = new List() { 2, 4, 5, 6 }; + Console.WriteLine(list[^2]); + var index = ^1; + Console.WriteLine(list[index]); + } +}"; + var verifier = CompileAndVerifyWithIndexAndRange(src, expectedOutput: @"5 +6"); + verifier.VerifyIL("C.Main", @" +{ + // Code size 106 (0x6a) + .maxstack 4 + .locals init (System.Index V_0, //index + int V_1, + int V_2, + System.Index V_3) + IL_0000: newobj ""System.Collections.Generic.List..ctor()"" + IL_0005: dup + IL_0006: ldc.i4.2 + IL_0007: callvirt ""void System.Collections.Generic.List.Add(int)"" + IL_000c: dup + IL_000d: ldc.i4.4 + IL_000e: callvirt ""void System.Collections.Generic.List.Add(int)"" + IL_0013: dup + IL_0014: ldc.i4.5 + IL_0015: callvirt ""void System.Collections.Generic.List.Add(int)"" + IL_001a: dup + IL_001b: ldc.i4.6 + IL_001c: callvirt ""void System.Collections.Generic.List.Add(int)"" + IL_0021: dup + IL_0022: dup + IL_0023: callvirt ""int System.Collections.Generic.List.Count.get"" + IL_0028: stloc.1 + IL_0029: ldc.i4.2 + IL_002a: ldc.i4.1 + IL_002b: newobj ""System.Index..ctor(int, bool)"" + IL_0030: stloc.3 + IL_0031: ldloca.s V_3 + IL_0033: ldloc.1 + IL_0034: call ""int System.Index.GetOffset(int)"" + IL_0039: stloc.2 + IL_003a: ldloc.2 + IL_003b: callvirt ""int System.Collections.Generic.List.this[int].get"" + IL_0040: call ""void System.Console.WriteLine(int)"" + IL_0045: ldloca.s V_0 + IL_0047: ldc.i4.1 + IL_0048: ldc.i4.1 + IL_0049: call ""System.Index..ctor(int, bool)"" + IL_004e: dup + IL_004f: callvirt ""int System.Collections.Generic.List.Count.get"" + IL_0054: stloc.2 + IL_0055: ldloca.s V_0 + IL_0057: ldloc.2 + IL_0058: call ""int System.Index.GetOffset(int)"" + IL_005d: stloc.1 + IL_005e: ldloc.1 + IL_005f: callvirt ""int System.Collections.Generic.List.this[int].get"" + IL_0064: call ""void System.Console.WriteLine(int)"" + IL_0069: ret +}"); + } + + [Theory] + [InlineData("Length")] + [InlineData("Count")] + public void PatternRangeIndexers(string propertyName) + { + var src = @" +using System; +class C +{ + private int[] _f = { 2, 4, 5, 6 }; + public int " + propertyName + @" => _f.Length; + public int[] Slice(int start, int length) => _f[start..length]; + static void Main() + { + var c = new C(); + foreach (var x in c[1..]) + { + Console.WriteLine(x); + } + foreach (var x in c[..^2]) + { + Console.WriteLine(x); + } + } +}"; + var verifier = CompileAndVerifyWithIndexAndRange(src, @" +4 +5 +2 +4"); + verifier.VerifyIL("C.Main", @" +{ + // Code size 197 (0xc5) + .maxstack 4 + .locals init (C V_0, //c + int[] V_1, + int V_2, + int V_3, + System.Range V_4, + int V_5, + int V_6, + System.Index V_7) + IL_0000: newobj ""C..ctor()"" + IL_0005: stloc.0 + IL_0006: ldloc.0 + IL_0007: dup + IL_0008: callvirt ""int C." + propertyName + @".get"" + IL_000d: stloc.3 + IL_000e: ldc.i4.1 + IL_000f: call ""System.Index System.Index.op_Implicit(int)"" + IL_0014: call ""System.Range System.Range.StartAt(System.Index)"" + IL_0019: stloc.s V_4 + IL_001b: ldloca.s V_4 + IL_001d: call ""System.Index System.Range.Start.get"" + IL_0022: stloc.s V_7 + IL_0024: ldloca.s V_7 + IL_0026: ldloc.3 + IL_0027: call ""int System.Index.GetOffset(int)"" + IL_002c: stloc.s V_5 + IL_002e: ldloca.s V_4 + IL_0030: call ""System.Index System.Range.End.get"" + IL_0035: stloc.s V_7 + IL_0037: ldloca.s V_7 + IL_0039: ldloc.3 + IL_003a: call ""int System.Index.GetOffset(int)"" + IL_003f: stloc.s V_6 + IL_0041: ldloc.s V_5 + IL_0043: ldloc.s V_6 + IL_0045: ldloc.s V_5 + IL_0047: sub + IL_0048: callvirt ""int[] C.Slice(int, int)"" + IL_004d: stloc.1 + IL_004e: ldc.i4.0 + IL_004f: stloc.2 + IL_0050: br.s IL_005e + IL_0052: ldloc.1 + IL_0053: ldloc.2 + IL_0054: ldelem.i4 + IL_0055: call ""void System.Console.WriteLine(int)"" + IL_005a: ldloc.2 + IL_005b: ldc.i4.1 + IL_005c: add + IL_005d: stloc.2 + IL_005e: ldloc.2 + IL_005f: ldloc.1 + IL_0060: ldlen + IL_0061: conv.i4 + IL_0062: blt.s IL_0052 + IL_0064: ldloc.0 + IL_0065: dup + IL_0066: callvirt ""int C." + propertyName + @".get"" + IL_006b: stloc.s V_6 + IL_006d: ldc.i4.2 + IL_006e: ldc.i4.1 + IL_006f: newobj ""System.Index..ctor(int, bool)"" + IL_0074: call ""System.Range System.Range.EndAt(System.Index)"" + IL_0079: stloc.s V_4 + IL_007b: ldloca.s V_4 + IL_007d: call ""System.Index System.Range.Start.get"" + IL_0082: stloc.s V_7 + IL_0084: ldloca.s V_7 + IL_0086: ldloc.s V_6 + IL_0088: call ""int System.Index.GetOffset(int)"" + IL_008d: stloc.s V_5 + IL_008f: ldloca.s V_4 + IL_0091: call ""System.Index System.Range.End.get"" + IL_0096: stloc.s V_7 + IL_0098: ldloca.s V_7 + IL_009a: ldloc.s V_6 + IL_009c: call ""int System.Index.GetOffset(int)"" + IL_00a1: stloc.3 + IL_00a2: ldloc.s V_5 + IL_00a4: ldloc.3 + IL_00a5: ldloc.s V_5 + IL_00a7: sub + IL_00a8: callvirt ""int[] C.Slice(int, int)"" + IL_00ad: stloc.1 + IL_00ae: ldc.i4.0 + IL_00af: stloc.2 + IL_00b0: br.s IL_00be + IL_00b2: ldloc.1 + IL_00b3: ldloc.2 + IL_00b4: ldelem.i4 + IL_00b5: call ""void System.Console.WriteLine(int)"" + IL_00ba: ldloc.2 + IL_00bb: ldc.i4.1 + IL_00bc: add + IL_00bd: stloc.2 + IL_00be: ldloc.2 + IL_00bf: ldloc.1 + IL_00c0: ldlen + IL_00c1: conv.i4 + IL_00c2: blt.s IL_00b2 + IL_00c4: ret +}"); + } + + [Theory] + [InlineData("Length")] + [InlineData("Count")] + public void PatternIndexIndexers(string propertyName) + { + var src = @" +using System; +class C +{ + private int[] _f = { 2, 4, 5, 6 }; + public int " + propertyName + @" => _f.Length; + public int this[int x] => _f[x]; + static void Main() + { + var c = new C(); + Console.WriteLine(c[0]); + Console.WriteLine(c[^1]); + } +}"; + var verifier = CompileAndVerifyWithIndexAndRange(src, @" +2 +6"); + verifier.VerifyIL("C.Main", @" +{ + // Code size 53 (0x35) + .maxstack 3 + .locals init (int V_0, + int V_1, + System.Index V_2) + IL_0000: newobj ""C..ctor()"" + IL_0005: dup + IL_0006: ldc.i4.0 + IL_0007: callvirt ""int C.this[int].get"" + IL_000c: call ""void System.Console.WriteLine(int)"" + IL_0011: dup + IL_0012: callvirt ""int C." + propertyName + @".get"" + IL_0017: stloc.0 + IL_0018: ldc.i4.1 + IL_0019: ldc.i4.1 + IL_001a: newobj ""System.Index..ctor(int, bool)"" + IL_001f: stloc.2 + IL_0020: ldloca.s V_2 + IL_0022: ldloc.0 + IL_0023: call ""int System.Index.GetOffset(int)"" + IL_0028: stloc.1 + IL_0029: ldloc.1 + IL_002a: callvirt ""int C.this[int].get"" + IL_002f: call ""void System.Console.WriteLine(int)"" + IL_0034: ret +}"); + } + [Fact] public void RefToArrayIndexIndexer() { @@ -108,10 +369,10 @@ class C { public static void Main() { - MyString s = ""abcdef""; + string s = ""abcdef""; Console.WriteLine(s[^2..]); } -}" + TestSources.StringWithIndexers, expectedOutput: "ef"); +}", expectedOutput: "ef"); } [Fact] @@ -123,10 +384,10 @@ class C { public static void Main() { - MyString s = ""abcdef""; + string s = ""abcdef""; Console.WriteLine(s[^4..^1]); } -}" + TestSources.StringWithIndexers, expectedOutput: "cde"); +}", expectedOutput: "cde"); } [Fact] @@ -141,12 +402,12 @@ public static void Main() var s = ""abcdef""; M(s); } - public static void M(MyString s) + public static void M(string s) { Console.WriteLine(s[new Index(1, false)]); Console.WriteLine(s[new Index(1, false), ^1]); } -}" + TestSources.StringWithIndexers); +}"); comp.VerifyDiagnostics( // (13,27): error CS1501: No overload for method 'this' takes 2 arguments // Console.WriteLine(s[new Index(1, false), ^1]); @@ -189,32 +450,49 @@ class C public static void Main() { var s = ""abcdef""; - M(s); - } - public static void M(MyString s) - { Console.WriteLine(s[new Index(1, false)]); Console.WriteLine(s[^1]); } -}" + TestSources.StringWithIndexers, expectedOutput: @"b +}", expectedOutput: @"b f"); - verifier.VerifyIL("C.M", @" + verifier.VerifyIL("C.Main", @" { - // Code size 39 (0x27) - .maxstack 3 - IL_0000: ldarga.s V_0 - IL_0002: ldc.i4.1 - IL_0003: ldc.i4.0 - IL_0004: newobj ""System.Index..ctor(int, bool)"" - IL_0009: call ""char MyString.this[System.Index].get"" - IL_000e: call ""void System.Console.WriteLine(char)"" - IL_0013: ldarga.s V_0 - IL_0015: ldc.i4.1 - IL_0016: ldc.i4.1 - IL_0017: newobj ""System.Index..ctor(int, bool)"" - IL_001c: call ""char MyString.this[System.Index].get"" - IL_0021: call ""void System.Console.WriteLine(char)"" - IL_0026: ret + // Code size 77 (0x4d) + .maxstack 4 + .locals init (int V_0, + int V_1, + System.Index V_2) + IL_0000: ldstr ""abcdef"" + IL_0005: dup + IL_0006: dup + IL_0007: callvirt ""int string.Length.get"" + IL_000c: stloc.0 + IL_000d: ldc.i4.1 + IL_000e: ldc.i4.0 + IL_000f: newobj ""System.Index..ctor(int, bool)"" + IL_0014: stloc.2 + IL_0015: ldloca.s V_2 + IL_0017: ldloc.0 + IL_0018: call ""int System.Index.GetOffset(int)"" + IL_001d: stloc.1 + IL_001e: ldloc.1 + IL_001f: callvirt ""char string.this[int].get"" + IL_0024: call ""void System.Console.WriteLine(char)"" + IL_0029: dup + IL_002a: callvirt ""int string.Length.get"" + IL_002f: stloc.1 + IL_0030: ldc.i4.1 + IL_0031: ldc.i4.1 + IL_0032: newobj ""System.Index..ctor(int, bool)"" + IL_0037: stloc.2 + IL_0038: ldloca.s V_2 + IL_003a: ldloc.1 + IL_003b: call ""int System.Index.GetOffset(int)"" + IL_0040: stloc.0 + IL_0041: ldloc.0 + IL_0042: callvirt ""char string.this[int].get"" + IL_0047: call ""void System.Console.WriteLine(char)"" + IL_004c: ret }"); } @@ -228,23 +506,49 @@ class C public static void Main() { var s = ""abcdef""; - var result = M(s); - Console.WriteLine(result); + Console.WriteLine(s[1..3]); } - public static string M(MyString s) => s[1..3]; -}" + TestSources.StringWithIndexers, expectedOutput: "bc"); - verifier.VerifyIL("C.M", @" +}", expectedOutput: "bc"); + verifier.VerifyIL("C.Main", @" { - // Code size 25 (0x19) - .maxstack 3 - IL_0000: ldarga.s V_0 - IL_0002: ldc.i4.1 - IL_0003: call ""System.Index System.Index.op_Implicit(int)"" - IL_0008: ldc.i4.3 - IL_0009: call ""System.Index System.Index.op_Implicit(int)"" - IL_000e: newobj ""System.Range..ctor(System.Index, System.Index)"" - IL_0013: call ""string MyString.this[System.Range].get"" - IL_0018: ret + // Code size 82 (0x52) + .maxstack 4 + .locals init (int V_0, + System.Range V_1, + int V_2, + int V_3, + System.Index V_4) + IL_0000: ldstr ""abcdef"" + IL_0005: dup + IL_0006: callvirt ""int string.Length.get"" + IL_000b: stloc.0 + IL_000c: ldloca.s V_1 + IL_000e: ldc.i4.1 + IL_000f: call ""System.Index System.Index.op_Implicit(int)"" + IL_0014: ldc.i4.3 + IL_0015: call ""System.Index System.Index.op_Implicit(int)"" + IL_001a: call ""System.Range..ctor(System.Index, System.Index)"" + IL_001f: ldloca.s V_1 + IL_0021: call ""System.Index System.Range.Start.get"" + IL_0026: stloc.s V_4 + IL_0028: ldloca.s V_4 + IL_002a: ldloc.0 + IL_002b: call ""int System.Index.GetOffset(int)"" + IL_0030: stloc.2 + IL_0031: ldloca.s V_1 + IL_0033: call ""System.Index System.Range.End.get"" + IL_0038: stloc.s V_4 + IL_003a: ldloca.s V_4 + IL_003c: ldloc.0 + IL_003d: call ""int System.Index.GetOffset(int)"" + IL_0042: stloc.3 + IL_0043: ldloc.2 + IL_0044: ldloc.3 + IL_0045: ldloc.2 + IL_0046: sub + IL_0047: callvirt ""string string.Substring(int, int)"" + IL_004c: call ""void System.Console.WriteLine(string)"" + IL_0051: ret }"); } @@ -261,8 +565,8 @@ public static void Main() var result = M(s); Console.WriteLine(result); } - public static string M(MyString s) => s[1..]; -}" + TestSources.StringWithIndexers, expectedOutput: "bcdef"); + public static string M(string s) => s[1..]; +}", expectedOutput: "bcdef"); } [Fact] @@ -278,8 +582,8 @@ public static void Main() var result = M(s); Console.WriteLine(result); } - public static string M(MyString s) => s[..^2]; -}" + TestSources.StringWithIndexers, expectedOutput: "abcd"); + public static string M(string s) => s[..^2]; +}", expectedOutput: "abcd"); } [Fact] @@ -1515,6 +1819,7 @@ static Index Create(int x, out int y) }", options: TestOptions.ReleaseExe), expectedOutput: "YES"); } + private const string PrintIndexesAndRangesCode = @" partial class Program { diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFromEndIndexOperation_IRangeOperation.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFromEndIndexOperation_IRangeOperation.cs index e0aca6c42ea7d..45555d08dd976 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFromEndIndexOperation_IRangeOperation.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFromEndIndexOperation_IRangeOperation.cs @@ -11,6 +11,102 @@ public partial class IOperationTests : SemanticModelTestBase // The tests in this file right now are just to verify that we do not assert in the CFG builder. These need to be expanded. // https://github.com/dotnet/roslyn/issues/31545 + [Fact] + public void PatternIndexAndRangeIndexer() + { + var src = @" +class C +{ + public int Length => 0; + public int this[int i] => i; + public int Slice(int i, int j) => i; + public void M() + /**/ +}"; + var comp = CreateCompilationWithIndexAndRange(src); + const string expectedOperationTree = @" +IBlockOperation (2 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: '_ = this[^0];') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: '_ = this[^0]') + Left: + IDiscardOperation (Symbol: System.Int32 _) (OperationKind.Discard, Type: System.Int32) (Syntax: '_') + Right: + IOperation: (OperationKind.None, Type: null) (Syntax: 'this[^0]') + Children(2): + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') + IUnaryOperation (UnaryOperatorKind.Hat) (OperationKind.Unary, Type: System.Index) (Syntax: '^0') + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0') + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: '_ = this[0..];') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: '_ = this[0..]') + Left: + IDiscardOperation (Symbol: System.Int32 _) (OperationKind.Discard, Type: System.Int32) (Syntax: '_') + Right: + IOperation: (OperationKind.None, Type: null) (Syntax: 'this[0..]') + Children(2): + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') + IRangeOperation (OperationKind.Range, Type: System.Range) (Syntax: '0..') + LeftOperand: + IConversionOperation (TryCast: False, Unchecked) (OperatorMethod: System.Index System.Index.op_Implicit(System.Int32 value)) (OperationKind.Conversion, Type: System.Index, IsImplicit) (Syntax: '0') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: True) (MethodSymbol: System.Index System.Index.op_Implicit(System.Int32 value)) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0') + RightOperand: + null"; + VerifyOperationTreeAndDiagnosticsForTest(comp, expectedOperationTree, DiagnosticDescription.None); + + VerifyOperationTreeAndDiagnosticsForTest(comp, expectedOperationTree, DiagnosticDescription.None); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (2) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: '_ = this[^0];') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: '_ = this[^0]') + Left: + IDiscardOperation (Symbol: System.Int32 _) (OperationKind.Discard, Type: System.Int32) (Syntax: '_') + Right: + IOperation: (OperationKind.None, Type: null) (Syntax: 'this[^0]') + Children(2): + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') + IUnaryOperation (UnaryOperatorKind.Hat) (OperationKind.Unary, Type: System.Index) (Syntax: '^0') + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0') + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: '_ = this[0..];') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: '_ = this[0..]') + Left: + IDiscardOperation (Symbol: System.Int32 _) (OperationKind.Discard, Type: System.Int32) (Syntax: '_') + Right: + IOperation: (OperationKind.None, Type: null) (Syntax: 'this[0..]') + Children(2): + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') + IRangeOperation (OperationKind.Range, Type: System.Range) (Syntax: '0..') + LeftOperand: + IConversionOperation (TryCast: False, Unchecked) (OperatorMethod: System.Index System.Index.op_Implicit(System.Int32 value)) (OperationKind.Conversion, Type: System.Index, IsImplicit) (Syntax: '0') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: True) (MethodSymbol: System.Index System.Index.op_Implicit(System.Int32 value)) + (ImplicitUserDefined) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0') + RightOperand: + null + Next (Regular) Block[B2] +Block[B2] - Exit + Predecessors: [B1] + Statements (0)"; + + VerifyFlowGraphForTest(comp, expectedFlowGraph); + } + [Fact] public void FromEndIndexFlow_01() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/IndexAndRangeTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/IndexAndRangeTests.cs index e44ad5b28127e..e6fd00e552fcd 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/IndexAndRangeTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/IndexAndRangeTests.cs @@ -15,6 +15,163 @@ public class IndexAndRangeTests : CompilingTestBase private const string RangeEndAtSignature = "System.Range System.Range.EndAt(System.Index end)"; private const string RangeAllSignature = "System.Range System.Range.All.get"; + + [Fact] + public void PatternIndexAndRangeNoGetOffset() + { + var src = @" +namespace System +{ + public struct Index + { + public Index(int value, bool fromEnd) { } + public static implicit operator Index(int value) => default; + } + public struct Range + { + public Range(Index start, Index end) { } + } +} +class C +{ + public int Length => 0; + public int this[int i] => 0; + public int Slice(int i, int j) => 0; + void M() + { + _ = this[^0]; + _ = this[0..]; + } +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // At binding time we don't look for all the necessary members + // on the Index and Range types. + ); + comp.VerifyEmitDiagnostics( + // (20,5): error CS0656: Missing compiler required member 'System.Index.GetOffset' + // { + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"{ + _ = this[^0]; + _ = this[0..]; + }").WithArguments("System.Index", "GetOffset").WithLocation(20, 5)); + } + + [Theory] + [InlineData("Start")] + [InlineData("End")] + public void PatternIndexAndRangeNoStartAndEnd(string propertyName) + { + var src = @" +namespace System +{ + public struct Index + { + public Index(int value, bool fromEnd) { } + public static implicit operator Index(int value) => default; + public int GetOffset(int length) => 0; + } + public struct Range + { + public Range(Index start, Index end) { } + public Index " + propertyName + @" => default; + } +} +class C +{ + public int Length => 0; + public int this[int i] => 0; + public int Slice(int i, int j) => 0; + void M() + { + _ = this[^0]; + _ = this[0..]; + } +}"; + var comp = CreateCompilation(src); + + comp.VerifyDiagnostics( + // At binding time we don't look for all the necessary members + // on the Index and Range types. + ); + + comp.VerifyEmitDiagnostics( + // (22,5): error CS0656: Missing compiler required member 'System.Range.get_{propertyName}' + // { + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"{ + _ = this[^0]; + _ = this[0..]; + }").WithArguments("System.Range", "get_" + (propertyName == "Start" ? "End" : "Start")).WithLocation(22, 5)); + } + + [Fact] + public void PatternIndexAndRangeNoOptionalParams() + { + var comp = CreateCompilationWithIndexAndRange(@" +class C +{ + public int Length => 0; + public int this[int i, int j = 0] => i; + public int Slice(int i, int j, int k = 0) => i; + public void M() + { + _ = this[^0]; + _ = this[0..]; + } +}"); + comp.VerifyDiagnostics( + // (9,18): error CS1503: Argument 1: cannot convert from 'System.Index' to 'int' + // _ = this[^0]; + Diagnostic(ErrorCode.ERR_BadArgType, "^0").WithArguments("1", "System.Index", "int").WithLocation(9, 18), + // (10,18): error CS1503: Argument 1: cannot convert from 'System.Range' to 'int' + // _ = this[0..]; + Diagnostic(ErrorCode.ERR_BadArgType, "0..").WithArguments("1", "System.Range", "int").WithLocation(10, 18)); + } + + [Fact] + public void PatternIndexAndRangeUseOriginalDefition() + { + var comp = CreateCompilationWithIndexAndRange(@" +struct S1 +{ + public int Length => 0; + public int this[T t] => 0; + public int Slice(T t, int j) => 0; +} +struct S2 +{ + public T Length => default; + public int this[int t] => 0; + public int Slice(int t, int j) => 0; +} +class C +{ + void M() + { + var s1 = new S1(); + _ = s1[^0]; + _ = s1[0..]; + + var s2 = new S2(); + _ = s2[^0]; + _ = s2[0..]; + } +}"); + comp.VerifyDiagnostics( + // (19,16): error CS1503: Argument 1: cannot convert from 'System.Index' to 'int' + // _ = s1[^0]; + Diagnostic(ErrorCode.ERR_BadArgType, "^0").WithArguments("1", "System.Index", "int").WithLocation(19, 16), + // (20,16): error CS1503: Argument 1: cannot convert from 'System.Range' to 'int' + // _ = s1[0..]; + Diagnostic(ErrorCode.ERR_BadArgType, "0..").WithArguments("1", "System.Range", "int").WithLocation(20, 16), + // (23,16): error CS1503: Argument 1: cannot convert from 'System.Index' to 'int' + // _ = s2[^0]; + Diagnostic(ErrorCode.ERR_BadArgType, "^0").WithArguments("1", "System.Index", "int").WithLocation(23, 16), + // (24,16): error CS1503: Argument 1: cannot convert from 'System.Range' to 'int' + // _ = s2[0..]; + Diagnostic(ErrorCode.ERR_BadArgType, "0..").WithArguments("1", "System.Range", "int").WithLocation(24, 16)); + } + [Fact] [WorkItem(31889, "https://github.com/dotnet/roslyn/issues/31889")] public void ArrayRangeIllegalRef() @@ -156,13 +313,7 @@ public void M(string s) var y = s[1..]; } }"); - comp.VerifyDiagnostics( - // (6,19): error CS1503: Argument 1: cannot convert from 'System.Index' to 'int' - // var x = s[^0]; - Diagnostic(ErrorCode.ERR_BadArgType, "^0").WithArguments("1", "System.Index", "int").WithLocation(6, 19), - // (7,19): error CS1503: Argument 1: cannot convert from 'System.Range' to 'int' - // var y = s[1..]; - Diagnostic(ErrorCode.ERR_BadArgType, "1..").WithArguments("1", "System.Range", "int").WithLocation(7, 19)); + comp.VerifyDiagnostics(); } [Fact] @@ -211,27 +362,6 @@ public class C { Diagnostic(ErrorCode.ERR_RefLvalueExpected, "^0").WithLocation(4, 25)); } - [Fact] - public void NetStandard20StringNoIndexRangeIndexers() - { - var comp = CreateCompilationWithIndexAndRange(@" -public class C -{ - public void M(string s) - { - var x = s[^2]; - var y = s[0..2]; - } -}"); - comp.VerifyDiagnostics( - // (6,19): error CS1503: Argument 1: cannot convert from 'System.Index' to 'int' - // var x = s[^2]; - Diagnostic(ErrorCode.ERR_BadArgType, "^2").WithArguments("1", "System.Index", "int").WithLocation(6, 19), - // (7,19): error CS1503: Argument 1: cannot convert from 'System.Range' to 'int' - // var y = s[0..2]; - Diagnostic(ErrorCode.ERR_BadArgType, "0..2").WithArguments("1", "System.Range", "int").WithLocation(7, 19)); - } - [Fact] public void IndexExpression_TypeNotFound() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index a63de06e94093..1fb03288c161c 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -1324,16 +1324,15 @@ unsafe public void Main() public void NullableRangeIndexer() { var text = @" -using System; #nullable enable class Program { - void M(int[] x, MyString m) + void M(int[] x, string m) { var y = x[1..3]; var z = m[1..3]; } -}" + TestSources.GetSubArray + TestSources.StringWithIndexers; +}" + TestSources.GetSubArray; CreateCompilationWithIndexAndRange(text).VerifyDiagnostics(); } diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelAPITests.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelAPITests.cs index c19db31f22ed7..1c2c89cfd4a61 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelAPITests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelAPITests.cs @@ -16,6 +16,45 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests { public class SemanticTests : CSharpTestBase { + [Fact] + public void PatternIndexAndRangeIndexers() + { + var comp = CreateCompilationWithIndexAndRange(@" +class C +{ + int Length => 0; + int this[int i] => i; + char Slice(int i, int j) => 'a'; + void M() + { + _ = this[^0]; + _ = this[0..]; + } +}"); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var root = tree.GetCompilationUnitRoot(); + + var accesses = root.DescendantNodes().OfType().ToList(); + var indexerAccess = accesses[0]; + + var typeInfo = model.GetTypeInfo(indexerAccess); + Assert.Equal(SpecialType.System_Int32, typeInfo.Type.SpecialType); + var symbolInfo = model.GetSymbolInfo(indexerAccess); + Assert.Null(symbolInfo.Symbol); + Assert.Empty(symbolInfo.CandidateSymbols); + + indexerAccess = accesses[1]; + + typeInfo = model.GetTypeInfo(indexerAccess); + Assert.Equal(SpecialType.System_Char, typeInfo.Type.SpecialType); + symbolInfo = model.GetSymbolInfo(indexerAccess); + Assert.Null(symbolInfo.Symbol); + Assert.Empty(symbolInfo.CandidateSymbols); + } + [Fact] public void RefReassignSymbolInfo() { diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 4f003152c24f7..62831169f8671 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -47,9 +47,11 @@ abstract Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.RegisterO abstract Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.RegisterOperationBlockStartAction(System.Action action) -> void abstract Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.RegisterSymbolEndAction(System.Action action) -> void abstract Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.RegisterSyntaxNodeAction(System.Action action, System.Collections.Immutable.ImmutableArray syntaxKinds) -> void +const Microsoft.CodeAnalysis.WellKnownMemberNames.CountPropertyName = "Count" -> string const Microsoft.CodeAnalysis.WellKnownMemberNames.DisposeAsyncMethodName = "DisposeAsync" -> string const Microsoft.CodeAnalysis.WellKnownMemberNames.DisposeMethodName = "Dispose" -> string const Microsoft.CodeAnalysis.WellKnownMemberNames.GetAsyncEnumeratorMethodName = "GetAsyncEnumerator" -> string +const Microsoft.CodeAnalysis.WellKnownMemberNames.LengthPropertyName = "Length" -> string const Microsoft.CodeAnalysis.WellKnownMemberNames.MoveNextAsyncMethodName = "MoveNextAsync" -> string Microsoft.CodeAnalysis.Compilation.HasImplicitConversion(Microsoft.CodeAnalysis.ITypeSymbol fromType, Microsoft.CodeAnalysis.ITypeSymbol toType) -> bool Microsoft.CodeAnalysis.Compilation.IsSymbolAccessibleWithin(Microsoft.CodeAnalysis.ISymbol symbol, Microsoft.CodeAnalysis.ISymbol within, Microsoft.CodeAnalysis.ITypeSymbol throughType = null) -> bool @@ -191,6 +193,7 @@ Microsoft.CodeAnalysis.Operations.ITryOperation.ExitLabel.get -> Microsoft.CodeA Microsoft.CodeAnalysis.Operations.IUsingOperation.Locals.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.Operations.UnaryOperatorKind.Hat = 7 -> Microsoft.CodeAnalysis.Operations.UnaryOperatorKind Microsoft.CodeAnalysis.SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier = 64 -> Microsoft.CodeAnalysis.SymbolDisplayMiscellaneousOptions +const Microsoft.CodeAnalysis.WellKnownMemberNames.SliceMethodName = "Slice" -> string override Microsoft.CodeAnalysis.FlowAnalysis.CaptureId.Equals(object obj) -> bool override Microsoft.CodeAnalysis.FlowAnalysis.CaptureId.GetHashCode() -> int static Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph.Create(Microsoft.CodeAnalysis.Operations.IBlockOperation body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph diff --git a/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs b/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs index 7355b8412c5bc..f09e0de2f9b19 100644 --- a/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs +++ b/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs @@ -323,5 +323,20 @@ public static class WellKnownMemberNames /// The required name for the DisposeAsync method used in an await using statement. /// public const string DisposeAsyncMethodName = "DisposeAsync"; + + /// + /// The required name for the Count property used in a pattern-based Index or Range indexer. + /// + public const string CountPropertyName = "Count"; + + /// + /// The required name for the Length property used in a pattern-based Index or Range indexer. + /// + public const string LengthPropertyName = "Length"; + + /// + /// The required name for the Slice method used in a pattern-based Range indexer. + /// + public const string SliceMethodName = "Slice"; } } diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index 8ccd9833493c9..e3a863115f9e9 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -414,6 +414,7 @@ internal enum WellKnownMember System_Runtime_CompilerServices_TupleElementNamesAttribute__ctorTransformNames, System_String__Format_IFormatProvider, + System_String__Substring, Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile, Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningMultipleFiles, @@ -451,6 +452,8 @@ internal enum WellKnownMember System_Range__StartAt, System_Range__EndAt, System_Range__get_All, + System_Range__get_Start, + System_Range__get_End, System_Runtime_CompilerServices_AsyncIteratorStateMachineAttribute__ctor, diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index 8751a032195cf..f387afbf8211b 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -2875,6 +2875,15 @@ static WellKnownMembers() (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, (byte)SignatureTypeCode.SZArray, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Object, + // System_String__Substring + (byte)MemberFlags.Method, // Flags + (byte)SpecialType.System_String, // DeclaringTypeId + 0, // Arity + 2, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, // Return Type + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, + // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile (byte)(MemberFlags.Method | MemberFlags.Static), // Flags (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.Microsoft_CodeAnalysis_Runtime_Instrumentation - WellKnownType.ExtSentinel), // DeclaringTypeId @@ -3105,6 +3114,20 @@ static WellKnownMembers() 0, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Range - WellKnownType.ExtSentinel), + // System_Range__get_Start + (byte)MemberFlags.PropertyGet, + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Range - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 0, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Index - WellKnownType.ExtSentinel), + + // System_Range__get_End + (byte)MemberFlags.PropertyGet, + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Range - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 0, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Index - WellKnownType.ExtSentinel), + // System_Runtime_CompilerServices_AsyncIteratorStateMachineAttribute__ctor (byte)MemberFlags.Constructor, // Flags (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Runtime_CompilerServices_AsyncIteratorStateMachineAttribute - WellKnownType.ExtSentinel), // DeclaringTypeId @@ -3731,6 +3754,7 @@ static WellKnownMembers() ".ctor", // System_Runtime_CompilerServices_TupleElementNamesAttribute__ctorTransformNames "Format", // System_String__Format_IFormatProvider + "Substring", // System_String__Substring "CreatePayload", // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile "CreatePayload", // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningMultipleFiles @@ -3763,6 +3787,8 @@ static WellKnownMembers() "StartAt", // System_Range__StartAt "EndAt", // System_Range__StartAt "get_All", // System_Range__get_All + "get_Start", // System_Range__get_Start + "get_End", // System_Range__get_End ".ctor", // System_Runtime_CompilerServices_AsyncIteratorStateMachineAttribute__ctor diff --git a/src/Compilers/Test/Utilities/CSharp/TestSources.cs b/src/Compilers/Test/Utilities/CSharp/TestSources.cs index df3af1a222bf3..fe6046c791925 100644 --- a/src/Compilers/Test/Utilities/CSharp/TestSources.cs +++ b/src/Compilers/Test/Utilities/CSharp/TestSources.cs @@ -385,40 +385,5 @@ public static T[] GetSubArray(T[] array, Range range) } } }"; - - // The references we use for System.String do not have an indexer for - // System.Index and System.Range, so this wrapper mimics the behavior - public const string StringWithIndexers = @" -internal readonly struct MyString -{ - private readonly string _s; - public MyString(string s) - { - _s = s; - } - public static implicit operator MyString(string s) => new MyString(s); - public static implicit operator string(MyString m) => m._s; - - public int Length => _s.Length; - - public char this[int index] => _s[index]; - - public char this[Index index] - { - get - { - int actualIndex = index.GetOffset(Length); - return this[actualIndex]; - } - } - - public string this[Range range] => Substring(range); - - public string Substring(Range range) - { - (int start, int length) = range.GetOffsetAndLength(Length); - return _s.Substring(start, length); - } -}"; } } From e6540648b4e0922ddbdec1e5e0800565d2e39781 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Thu, 11 Apr 2019 13:56:08 -0700 Subject: [PATCH 2/7] Respond to PR comments --- .../Portable/Binder/Binder_Expressions.cs | 82 ++++----- .../CSharp/Portable/BoundTree/BoundNodes.xml | 6 +- .../Semantic/Semantics/IndexAndRangeTests.cs | 168 ++++++++++++++++++ 3 files changed, 205 insertions(+), 51 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index db9c3873c84cb..8848f69c64672 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -7506,45 +7506,16 @@ private BoundExpression BindIndexedPropertyAccess(SyntaxNode syntax, BoundExpres var lookupResult = LookupResult.GetInstance(); HashSet useSiteDiagnostics = null; - // look for Length first - LookupMembersInType( - lookupResult, - receiverType, - WellKnownMemberNames.LengthPropertyName, - arity: 0, - basesBeingResolved: null, - LookupOptions.Default, - originalBinder: this, - diagnose: false, - useSiteDiagnostics: ref useSiteDiagnostics); - - if (!lookupSatisfiesLengthPattern(out lengthOrCountProperty)) - { - // Look for Count second - lookupResult.Clear(); - useSiteDiagnostics?.Clear(); - LookupMembersInType( - lookupResult, - receiverType, - WellKnownMemberNames.CountPropertyName, - arity: 0, - basesBeingResolved: null, - LookupOptions.Default, - originalBinder: this, - diagnose: false, - ref useSiteDiagnostics); + // Look for Length first - if (!lookupSatisfiesLengthPattern(out lengthOrCountProperty)) - { - cleanup(); - patternIndexerAccess = null; - return false; - } + if (!tryLookupLengthOrCount(WellKnownMemberNames.LengthPropertyName, out lengthOrCountProperty) && + !tryLookupLengthOrCount(WellKnownMemberNames.CountPropertyName, out lengthOrCountProperty)) + { + patternIndexerAccess = null; + return false; } Debug.Assert(lengthOrCountProperty is { }); - lookupResult.Clear(); - useSiteDiagnostics?.Clear(); if (argIsIndex) { @@ -7566,9 +7537,9 @@ private BoundExpression BindIndexedPropertyAccess(SyntaxNode syntax, BoundExpres foreach (var candidate in lookupResult.Symbols) { if (!candidate.IsStatic && - IsAccessible(candidate, ref useSiteDiagnostics) && candidate is PropertySymbol property && property.GetOwnOrInheritedGetMethod() is MethodSymbol getMethod && + IsAccessible(getMethod, ref useSiteDiagnostics) && getMethod.OriginalDefinition is var original && original.ParameterCount == 1 && isIntNotByRef(original.Parameters[0])) @@ -7580,7 +7551,7 @@ private BoundExpression BindIndexedPropertyAccess(SyntaxNode syntax, BoundExpres getMethod, arguments[0], getMethod.ReturnType); - cleanup(); + cleanup(lookupResult, ref useSiteDiagnostics); return true; } } @@ -7600,7 +7571,7 @@ private BoundExpression BindIndexedPropertyAccess(SyntaxNode syntax, BoundExpres substring, arguments[0], substring.ReturnType); - cleanup(); + cleanup(lookupResult, ref useSiteDiagnostics); return true; } } @@ -7639,40 +7610,55 @@ private BoundExpression BindIndexedPropertyAccess(SyntaxNode syntax, BoundExpres method, arguments[0], method.ReturnType); - cleanup(); + cleanup(lookupResult, ref useSiteDiagnostics); return true; } } } } - cleanup(); + cleanup(lookupResult, ref useSiteDiagnostics); patternIndexerAccess = null; return false; - bool isIntNotByRef(ParameterSymbol param) - => param.Type.SpecialType == SpecialType.System_Int32 && - param.RefKind == RefKind.None; - - void cleanup() + static void cleanup(LookupResult lookupResult, ref HashSet useSiteDiagnostics) { lookupResult.Free(); useSiteDiagnostics = null; } - bool lookupSatisfiesLengthPattern(out PropertySymbol valid) + bool isIntNotByRef(ParameterSymbol param) + => param.Type.SpecialType == SpecialType.System_Int32 && + param.RefKind == RefKind.None; + + bool tryLookupLengthOrCount(string propertyName, out PropertySymbol valid) { + LookupMembersInType( + lookupResult, + receiverType, + propertyName, + arity: 0, + basesBeingResolved: null, + LookupOptions.Default, + originalBinder: this, + diagnose: false, + useSiteDiagnostics: ref useSiteDiagnostics); + if (lookupResult.IsSingleViable && - lookupResult.SingleSymbolOrDefault is PropertySymbol property && - property.GetOwnOrInheritedGetMethod().OriginalDefinition is MethodSymbol getMethod && + lookupResult.Symbols[0] is PropertySymbol property && + property.GetOwnOrInheritedGetMethod()?.OriginalDefinition is MethodSymbol getMethod && getMethod.ReturnType.SpecialType == SpecialType.System_Int32 && getMethod.RefKind == RefKind.None && !getMethod.IsStatic && IsAccessible(getMethod, ref useSiteDiagnostics)) { + lookupResult.Clear(); + useSiteDiagnostics = null; valid = property; return true; } + lookupResult.Clear(); + useSiteDiagnostics = null; valid = null; return false; } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index fab7619e9a363..7aa18d4f2cec6 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -1748,9 +1748,9 @@ - - - + + + diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/IndexAndRangeTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/IndexAndRangeTests.cs index e6fd00e552fcd..a0ea3cac3444f 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/IndexAndRangeTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/IndexAndRangeTests.cs @@ -15,6 +15,174 @@ public class IndexAndRangeTests : CompilingTestBase private const string RangeEndAtSignature = "System.Range System.Range.EndAt(System.Index end)"; private const string RangeAllSignature = "System.Range System.Range.All.get"; + [Fact] + public void PatternIndexAndRangeLengthInaccessible() + { + var src = @" +class B +{ + private int Length => 0; + public int this[int i] => 0; + public int Slice(int i, int j) => 0; +} +class C +{ + void M(B b) + { + _ = b[^0]; + _ = b[0..]; + } +}"; + var comp = CreateCompilationWithIndexAndRange(src); + comp.VerifyDiagnostics( + // (12,15): error CS1503: Argument 1: cannot convert from 'System.Index' to 'int' + // _ = b[^0]; + Diagnostic(ErrorCode.ERR_BadArgType, "^0").WithArguments("1", "System.Index", "int").WithLocation(12, 15), + // (13,15): error CS1503: Argument 1: cannot convert from 'System.Range' to 'int' + // _ = b[0..]; + Diagnostic(ErrorCode.ERR_BadArgType, "0..").WithArguments("1", "System.Range", "int").WithLocation(13, 15) + ); + } + + [Fact] + public void PatternIndexAndRangeLengthNoGetter() + { + var src = @" +class B +{ + public int Length { set { } } + public int this[int i] => 0; + public int Slice(int i, int j) => 0; +} +class C +{ + void M(B b) + { + _ = b[^0]; + _ = b[0..]; + } +}"; + var comp = CreateCompilationWithIndexAndRange(src); + comp.VerifyDiagnostics( + // (12,15): error CS1503: Argument 1: cannot convert from 'System.Index' to 'int' + // _ = b[^0]; + Diagnostic(ErrorCode.ERR_BadArgType, "^0").WithArguments("1", "System.Index", "int").WithLocation(12, 15), + // (13,15): error CS1503: Argument 1: cannot convert from 'System.Range' to 'int' + // _ = b[0..]; + Diagnostic(ErrorCode.ERR_BadArgType, "0..").WithArguments("1", "System.Range", "int").WithLocation(13, 15) + ); + } + + [Fact] + public void PatternIndexAndRangeGetLengthInaccessible() + { + var src = @" +class B +{ + public int Length { private get => 0; set { } } + public int this[int i] => 0; + public int Slice(int i, int j) => 0; +} +class C +{ + void M(B b) + { + _ = b[^0]; + _ = b[0..]; + } +}"; + var comp = CreateCompilationWithIndexAndRange(src); + comp.VerifyDiagnostics( + // (12,15): error CS1503: Argument 1: cannot convert from 'System.Index' to 'int' + // _ = b[^0]; + Diagnostic(ErrorCode.ERR_BadArgType, "^0").WithArguments("1", "System.Index", "int").WithLocation(12, 15), + // (13,15): error CS1503: Argument 1: cannot convert from 'System.Range' to 'int' + // _ = b[0..]; + Diagnostic(ErrorCode.ERR_BadArgType, "0..").WithArguments("1", "System.Range", "int").WithLocation(13, 15) + ); + } + + [Fact] + public void PatternIndexAndRangePatternMethodsInaccessible() + { + var src = @" +class B +{ + public int Length => 0; + public int this[int i] { private get => 0; set { } } + private int Slice(int i, int j) => 0; +} +class C +{ + void M(B b) + { + _ = b[^0]; + _ = b[0..]; + } +}"; + var comp = CreateCompilationWithIndexAndRange(src); + comp.VerifyDiagnostics( + // (12,15): error CS1503: Argument 1: cannot convert from 'System.Index' to 'int' + // _ = b[^0]; + Diagnostic(ErrorCode.ERR_BadArgType, "^0").WithArguments("1", "System.Index", "int").WithLocation(12, 15), + // (13,15): error CS1503: Argument 1: cannot convert from 'System.Range' to 'int' + // _ = b[0..]; + Diagnostic(ErrorCode.ERR_BadArgType, "0..").WithArguments("1", "System.Range", "int").WithLocation(13, 15) + ); + } + + [Fact] + public void PatternIndexAndRangeStaticLength() + { + var src = @" +class B +{ + public static int Length => 0; + public int this[int i] => 0; + private int Slice(int i, int j) => 0; +} +class C +{ + void M(B b) + { + _ = b[^0]; + _ = b[0..]; + } +}"; + var comp = CreateCompilationWithIndexAndRange(src); + comp.VerifyDiagnostics( + // (12,15): error CS1503: Argument 1: cannot convert from 'System.Index' to 'int' + // _ = b[^0]; + Diagnostic(ErrorCode.ERR_BadArgType, "^0").WithArguments("1", "System.Index", "int").WithLocation(12, 15), + // (13,15): error CS1503: Argument 1: cannot convert from 'System.Range' to 'int' + // _ = b[0..]; + Diagnostic(ErrorCode.ERR_BadArgType, "0..").WithArguments("1", "System.Range", "int").WithLocation(13, 15) + ); + } + + [Fact] + public void PatternIndexAndRangeStaticSlice() + { + var src = @" +class B +{ + public int Length => 0; + private static int Slice(int i, int j) => 0; +} +class C +{ + void M(B b) + { + _ = b[0..]; + } +}"; + var comp = CreateCompilationWithIndexAndRange(src); + comp.VerifyDiagnostics( + // (11,13): error CS0021: Cannot apply indexing with [] to an expression of type 'B' + // _ = b[0..]; + Diagnostic(ErrorCode.ERR_BadIndexLHS, "b[0..]").WithArguments("B").WithLocation(11, 13) + ); + } [Fact] public void PatternIndexAndRangeNoGetOffset() From 91a549153d2f05d87c0f79833766757f929ad81e Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Thu, 11 Apr 2019 14:50:40 -0700 Subject: [PATCH 3/7] Respond to more PR comments --- .../Test/Emit/CodeGen/IndexAndRangeTests.cs | 103 ++++++++++-------- .../Symbol/Symbols/MissingSpecialMember.cs | 2 + 2 files changed, 61 insertions(+), 44 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs index 2cb18ac113dba..0fc135a1d9b73 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs @@ -16,6 +16,33 @@ private CompilationVerifier CompileAndVerifyWithIndexAndRange(string s, string e return CompileAndVerify(comp, expectedOutput: expectedOutput); } + [Fact] + public void RealIndexersPreferredToPattern() + { + var src = @" +using System; +class C +{ + public int Length => throw null; + public int this[Index i, int j = 0] { get { Console.WriteLine(""Index""); return 0; } } + public int this[int i] { get { Console.WriteLine(""int""); return 0; } } + public int Slice(int i, int j) => throw null; + public int this[Range r, int j = 0] { get { Console.WriteLine(""Range""); return 0; } } + + static void Main() + { + var c = new C(); + _ = c[0]; + _ = c[^0]; + _ = c[0..]; + } +}"; + var verifier = CompileAndVerifyWithIndexAndRange(src, expectedOutput: @" +int +Index +Range"); + } + [Fact] public void PatternIndexList() { @@ -24,9 +51,9 @@ public void PatternIndexList() using System.Collections.Generic; class C { + private static List list = new List() { 2, 4, 5, 6 }; static void Main() { - var list = new List() { 2, 4, 5, 6 }; Console.WriteLine(list[^2]); var index = ^1; Console.WriteLine(list[index]); @@ -36,55 +63,43 @@ static void Main() 6"); verifier.VerifyIL("C.Main", @" { - // Code size 106 (0x6a) - .maxstack 4 + // Code size 82 (0x52) + .maxstack 3 .locals init (System.Index V_0, //index int V_1, int V_2, System.Index V_3) - IL_0000: newobj ""System.Collections.Generic.List..ctor()"" + IL_0000: ldsfld ""System.Collections.Generic.List C.list"" IL_0005: dup - IL_0006: ldc.i4.2 - IL_0007: callvirt ""void System.Collections.Generic.List.Add(int)"" - IL_000c: dup - IL_000d: ldc.i4.4 - IL_000e: callvirt ""void System.Collections.Generic.List.Add(int)"" - IL_0013: dup - IL_0014: ldc.i4.5 - IL_0015: callvirt ""void System.Collections.Generic.List.Add(int)"" - IL_001a: dup - IL_001b: ldc.i4.6 - IL_001c: callvirt ""void System.Collections.Generic.List.Add(int)"" - IL_0021: dup - IL_0022: dup - IL_0023: callvirt ""int System.Collections.Generic.List.Count.get"" - IL_0028: stloc.1 - IL_0029: ldc.i4.2 + IL_0006: callvirt ""int System.Collections.Generic.List.Count.get"" + IL_000b: stloc.1 + IL_000c: ldc.i4.2 + IL_000d: ldc.i4.1 + IL_000e: newobj ""System.Index..ctor(int, bool)"" + IL_0013: stloc.3 + IL_0014: ldloca.s V_3 + IL_0016: ldloc.1 + IL_0017: call ""int System.Index.GetOffset(int)"" + IL_001c: stloc.2 + IL_001d: ldloc.2 + IL_001e: callvirt ""int System.Collections.Generic.List.this[int].get"" + IL_0023: call ""void System.Console.WriteLine(int)"" + IL_0028: ldloca.s V_0 IL_002a: ldc.i4.1 - IL_002b: newobj ""System.Index..ctor(int, bool)"" - IL_0030: stloc.3 - IL_0031: ldloca.s V_3 - IL_0033: ldloc.1 - IL_0034: call ""int System.Index.GetOffset(int)"" - IL_0039: stloc.2 - IL_003a: ldloc.2 - IL_003b: callvirt ""int System.Collections.Generic.List.this[int].get"" - IL_0040: call ""void System.Console.WriteLine(int)"" - IL_0045: ldloca.s V_0 - IL_0047: ldc.i4.1 - IL_0048: ldc.i4.1 - IL_0049: call ""System.Index..ctor(int, bool)"" - IL_004e: dup - IL_004f: callvirt ""int System.Collections.Generic.List.Count.get"" - IL_0054: stloc.2 - IL_0055: ldloca.s V_0 - IL_0057: ldloc.2 - IL_0058: call ""int System.Index.GetOffset(int)"" - IL_005d: stloc.1 - IL_005e: ldloc.1 - IL_005f: callvirt ""int System.Collections.Generic.List.this[int].get"" - IL_0064: call ""void System.Console.WriteLine(int)"" - IL_0069: ret + IL_002b: ldc.i4.1 + IL_002c: call ""System.Index..ctor(int, bool)"" + IL_0031: ldsfld ""System.Collections.Generic.List C.list"" + IL_0036: dup + IL_0037: callvirt ""int System.Collections.Generic.List.Count.get"" + IL_003c: stloc.2 + IL_003d: ldloca.s V_0 + IL_003f: ldloc.2 + IL_0040: call ""int System.Index.GetOffset(int)"" + IL_0045: stloc.1 + IL_0046: ldloc.1 + IL_0047: callvirt ""int System.Collections.Generic.List.this[int].get"" + IL_004c: call ""void System.Console.WriteLine(int)"" + IL_0051: ret }"); } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs index 4ac4117b70806..a1061f74d95cb 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs @@ -911,6 +911,8 @@ public void AllWellKnownTypeMembers() case WellKnownMember.System_Range__StartAt: case WellKnownMember.System_Range__EndAt: case WellKnownMember.System_Range__get_All: + case WellKnownMember.System_Range__get_Start: + case WellKnownMember.System_Range__get_End: case WellKnownMember.System_Runtime_CompilerServices_RuntimeHelpers__GetSubArray_T: case WellKnownMember.System_Runtime_CompilerServices_AsyncIteratorStateMachineAttribute__ctor: case WellKnownMember.System_IAsyncDisposable__DisposeAsync: From 9a11b9a319c3cee2968a7980fa4b72d16ba1c104 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Thu, 11 Apr 2019 15:34:34 -0700 Subject: [PATCH 4/7] Fix method baseline --- .../Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb index b573c5871eab8..a61db03addc4c 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb @@ -688,6 +688,8 @@ End Namespace WellKnownMember.System_Range__EndAt, WellKnownMember.System_Range__get_All, WellKnownMember.System_Range__StartAt, + WellKnownMember.System_Range__get_End, + WellKnownMember.System_Range__get_Start, WellKnownMember.System_Runtime_CompilerServices_IsUnmanagedAttribute__ctor, WellKnownMember.System_Runtime_CompilerServices_ITuple__get_Item, WellKnownMember.System_Runtime_CompilerServices_ITuple__get_Length, From 735de152047d70cdb5bb7af9ddaef642677014b3 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Thu, 11 Apr 2019 17:06:41 -0700 Subject: [PATCH 5/7] Fully fix baseline --- .../Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb index a61db03addc4c..cb83be8cb5497 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb @@ -825,6 +825,8 @@ End Namespace WellKnownMember.System_Range__EndAt, WellKnownMember.System_Range__get_All, WellKnownMember.System_Range__StartAt, + WellKnownMember.System_Range__get_End, + WellKnownMember.System_Range__get_Start, WellKnownMember.System_Runtime_CompilerServices_IsUnmanagedAttribute__ctor, WellKnownMember.System_Runtime_CompilerServices_ITuple__get_Item, WellKnownMember.System_Runtime_CompilerServices_ITuple__get_Length, From d90d5dd9cbbb6504d8a1a33c4637c9e72817f034 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Thu, 11 Apr 2019 18:24:13 -0700 Subject: [PATCH 6/7] Move Substring from being a WellKnownMember to a SpecialMember --- .../CSharp/Portable/Binder/Binder_Expressions.cs | 2 +- src/Compilers/Core/Portable/SpecialMember.cs | 1 + src/Compilers/Core/Portable/SpecialMembers.cs | 10 ++++++++++ src/Compilers/Core/Portable/WellKnownMember.cs | 1 - src/Compilers/Core/Portable/WellKnownMembers.cs | 10 ---------- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 8848f69c64672..82808a0d0dffa 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -7561,7 +7561,7 @@ private BoundExpression BindIndexedPropertyAccess(SyntaxNode syntax, BoundExpres { Debug.Assert(argIsRange); // Look for Substring - var substring = (MethodSymbol)Compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Substring); + var substring = (MethodSymbol)Compilation.GetSpecialTypeMember(SpecialMember.System_String__Substring); if (substring is { }) { patternIndexerAccess = new BoundIndexOrRangePatternIndexerAccess( diff --git a/src/Compilers/Core/Portable/SpecialMember.cs b/src/Compilers/Core/Portable/SpecialMember.cs index 9eb714c783123..94d8f6119b93b 100644 --- a/src/Compilers/Core/Portable/SpecialMember.cs +++ b/src/Compilers/Core/Portable/SpecialMember.cs @@ -22,6 +22,7 @@ internal enum SpecialMember System_String__Length, System_String__Chars, System_String__Format, + System_String__Substring, System_Double__IsNaN, System_Single__IsNaN, diff --git a/src/Compilers/Core/Portable/SpecialMembers.cs b/src/Compilers/Core/Portable/SpecialMembers.cs index 3e82445bd74c9..bf31714bd22c8 100644 --- a/src/Compilers/Core/Portable/SpecialMembers.cs +++ b/src/Compilers/Core/Portable/SpecialMembers.cs @@ -137,6 +137,15 @@ static SpecialMembers() (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, (byte)SignatureTypeCode.SZArray, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Object, + // System_String__Substring + (byte)MemberFlags.Method, // Flags + (byte)SpecialType.System_String, // DeclaringTypeId + 0, // Arity + 2, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, // Return Type + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, + // System_Double__IsNaN (byte)(MemberFlags.Method | MemberFlags.Static), // Flags (byte)SpecialType.System_Double, // DeclaringTypeId @@ -1005,6 +1014,7 @@ static SpecialMembers() "get_Length", // System_String__Length "get_Chars", // System_String__Chars "Format", // System_String__Format + "Substring", // System_String__Substring "IsNaN", // System_Double__IsNaN "IsNaN", // System_Single__IsNaN "Combine", // System_Delegate__Combine diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index e3a863115f9e9..ec122446800b8 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -414,7 +414,6 @@ internal enum WellKnownMember System_Runtime_CompilerServices_TupleElementNamesAttribute__ctorTransformNames, System_String__Format_IFormatProvider, - System_String__Substring, Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile, Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningMultipleFiles, diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index f387afbf8211b..bd6f356b402f3 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -2875,15 +2875,6 @@ static WellKnownMembers() (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, (byte)SignatureTypeCode.SZArray, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Object, - // System_String__Substring - (byte)MemberFlags.Method, // Flags - (byte)SpecialType.System_String, // DeclaringTypeId - 0, // Arity - 2, // Method Signature - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, // Return Type - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, - // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile (byte)(MemberFlags.Method | MemberFlags.Static), // Flags (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.Microsoft_CodeAnalysis_Runtime_Instrumentation - WellKnownType.ExtSentinel), // DeclaringTypeId @@ -3754,7 +3745,6 @@ static WellKnownMembers() ".ctor", // System_Runtime_CompilerServices_TupleElementNamesAttribute__ctorTransformNames "Format", // System_String__Format_IFormatProvider - "Substring", // System_String__Substring "CreatePayload", // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile "CreatePayload", // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningMultipleFiles From 39ee17cd02bfa4f8a934015ad2f1d411a8ff7727 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 12 Apr 2019 14:19:14 -0700 Subject: [PATCH 7/7] Use static local function --- src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 82808a0d0dffa..6692814229147 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -7627,7 +7627,7 @@ static void cleanup(LookupResult lookupResult, ref HashSet useSi useSiteDiagnostics = null; } - bool isIntNotByRef(ParameterSymbol param) + static bool isIntNotByRef(ParameterSymbol param) => param.Type.SpecialType == SpecialType.System_Int32 && param.RefKind == RefKind.None;