@@ -32,52 +32,30 @@ private static void SetExprArgument(TextWriter trapFile, Expression left, Expres
3232 trapFile . expr_argument ( right , 0 ) ;
3333 }
3434
35- private Expression MakeSubtractionExpression ( IExpressionParentEntity parent , int child )
35+ private Expression MakeZeroFromEndExpression ( IExpressionParentEntity parent , int child )
3636 {
3737 var info = new ExpressionInfo (
3838 Context ,
3939 AnnotatedTypeSymbol . CreateNotAnnotated ( Context . Compilation . GetSpecialType ( SpecialType . System_Int32 ) ) ,
4040 Location ,
41- ExprKind . SUB ,
41+ ExprKind . INDEX ,
4242 parent ,
4343 child ,
4444 isCompilerGenerated : true ,
4545 null ) ;
4646
47- return new Expression ( info ) ;
48- }
47+ var index = new Expression ( info ) ;
4948
50- private Expression MakeLengthPropertyCall ( TextWriter trapFile , IPropertySymbol lengthPropertySymbol , IExpressionParentEntity parent , int child )
51- {
52- var lengthInfo = new ExpressionInfo (
53- Context ,
54- AnnotatedTypeSymbol . CreateNotAnnotated ( Context . Compilation . GetSpecialType ( SpecialType . System_Int32 ) ) ,
55- Location ,
56- ExprKind . PROPERTY_ACCESS ,
57- parent ,
58- child ,
59- isCompilerGenerated : true ,
60- null ) ;
61- var length = new Expression ( lengthInfo ) ;
62- Create ( Context , qualifier , length , - 1 ) ;
63-
64- var lengthProp = Property . Create ( Context , lengthPropertySymbol ) ;
65- trapFile . expr_access ( length , lengthProp ) ;
66- return length ;
49+ MakeZeroLiteral ( index , 0 ) ;
50+ return index ;
6751 }
6852
69- private Expression CreateFromIndexExpression ( TextWriter trapFile , IPropertySymbol lengthPropertySymbol , IExpressionParentEntity parent , int child , PrefixUnaryExpressionSyntax index )
53+ private Expression MakeZeroLiteral ( IExpressionParentEntity parent , int child )
7054 {
71- var sub = MakeSubtractionExpression ( parent , child ) ;
72- MakeLengthPropertyCall ( trapFile , lengthPropertySymbol , sub , 0 ) ;
73- var info = new ExpressionNodeInfo ( Context , index . Operand , sub , 1 )
74- {
75- IsCompilerGenerated = true
76- } ;
77- Factory . Create ( info ) ;
78- return sub ;
55+ return Literal . CreateGenerated ( Context , parent , child , Context . Compilation . GetSpecialType ( SpecialType . System_Int32 ) , 0 , Location ) ;
7956 }
8057
58+
8159 /// <summary>
8260 /// It is assumed that either the input is
8361 /// 1. A normal expression that can be used as endpoint (e.g a constant like "3").
@@ -87,18 +65,16 @@ private Expression CreateFromIndexExpression(TextWriter trapFile, IPropertySymbo
8765 /// <param name="parent">The parent expression entity.</param>
8866 /// <param name="child">The child index within the parent.</param>
8967 /// <returns>An expression representing the endpoint of a range to be used in conjunction with a slice operation.</returns>
90- private Expression CreateFromRangeEndpoint ( TextWriter trapFile , IPropertySymbol lengthPropertySymbol , ExpressionSyntax syntax , IExpressionParentEntity parent , int child )
68+ private Expression MakeFromRangeEndpoint ( ExpressionSyntax syntax , IExpressionParentEntity parent , int child )
9169 {
92- if ( syntax . Kind ( ) == SyntaxKind . IndexExpression && syntax is PrefixUnaryExpressionSyntax index )
93- {
94- return CreateFromIndexExpression ( trapFile , lengthPropertySymbol , parent , child , index ) ;
95- }
96-
9770 var info = new ExpressionNodeInfo ( Context , syntax , parent , child )
9871 {
9972 IsCompilerGenerated = true
10073 } ;
101- return Factory . Create ( info ) ;
74+
75+ return syntax . Kind ( ) == SyntaxKind . IndexExpression
76+ ? PrefixUnary . Create ( info . SetKind ( ExprKind . INDEX ) )
77+ : Factory . Create ( info ) ;
10278 }
10379
10480 /// <summary>
@@ -107,14 +83,9 @@ private Expression CreateFromRangeEndpoint(TextWriter trapFile, IPropertySymbol
10783 /// </summary>
10884 /// <param name="method">The method symbol to check.</param>
10985 /// <returns>True if the method is a slice method; false otherwise.</returns>
110- private bool IsSliceWithRange ( IMethodSymbol method , [ NotNullWhen ( true ) ] out IPropertySymbol ? lengthPropertySymbol , [ NotNullWhen ( true ) ] out RangeExpressionSyntax ? range )
86+ private bool IsSliceWithRange ( IMethodSymbol method , [ NotNullWhen ( true ) ] out RangeExpressionSyntax ? range )
11187 {
11288 range = null ;
113- lengthPropertySymbol = method
114- . ContainingType
115- . GetMembers ( "Length" )
116- . OfType < IPropertySymbol > ( )
117- . FirstOrDefault ( ) ;
11889
11990 if ( argumentList . Arguments . Count == 1 )
12091 {
@@ -123,63 +94,42 @@ private bool IsSliceWithRange(IMethodSymbol method, [NotNullWhen(true)] out IPro
12394
12495 return ( method . Name == "Slice" || method . Name == "Substring" )
12596 && method . Parameters . Length == 2
126- && lengthPropertySymbol is not null
12797 && range is not null ;
12898 }
12999
130100 /// <summary>
131- /// Populates a slice method call based on the given range and length property symbol.
101+ /// Populates a slice method call based on the given range.
102+ /// Roslyn translates indexer accesses with range expressions in the following way.
103+ /// 1. s[a..b] -> s.Slice(a, b - a)
104+ /// 2. s[..b] -> s.Slice(0, b)
105+ /// 3. s[a..] -> s.Slice(a, s.Length - a)
106+ /// 4. s[..] -> s.Slice(0, s.Length)
107+ /// However, it is possible that both the qualifier or the index endpoints may contain method calls.
108+ /// If we want to translate this accurately, we would need to introduce synthetic statements for qualifier and
109+ /// the endpoints, which should then be used in the slice method call.
110+ /// To avoid this, we translate as follows.
111+ /// 1. s[a..b] -> s.Slice(a, b)
112+ /// 2. s[..b] -> s.Slice(0, b)
113+ /// 3. s[a..] -> s.Slice(a, ^0)
114+ /// 4. s[..] -> s.Slice(0, ^0)
115+ ///
116+ /// Even though index expressions can't technically be used in this way, they signal that we
117+ /// could perceive ^b as "length - b".
132118 /// </summary>
133119 /// <param name="trapFile">The trap file to write to.</param>
134- /// <param name="lengthPropertySymbol">The length property symbol.</param>
135120 /// <param name="slice">The slice method symbol.</param>
136121 /// <param name="range">The range expression syntax.</param>
137- private void PopulateSlice ( TextWriter trapFile , IPropertySymbol lengthPropertySymbol , IMethodSymbol slice , RangeExpressionSyntax range )
122+ private void PopulateSlice ( TextWriter trapFile , IMethodSymbol slice , RangeExpressionSyntax range )
138123 {
139- // 1. s[a..b] -> s.Slice(a, b - a)
140- // 2. s[..b] -> s.Slice(0, b)
141- // 3. s[a..] -> s.Slice(a, s.Length - a)
142- // 4. s[..] -> s.Slice(0, s.Length)
143- // Furthermore, note that uses of index expressions (e.g. s[2..^1]) within the range
144- // get translated to length - index, so we need to handle this as well.
145- switch ( range . LeftOperand , range . RightOperand )
146- {
147- case ( ExpressionSyntax lsyntax , ExpressionSyntax rsyntax ) :
148- {
149- var left = CreateFromRangeEndpoint ( trapFile , lengthPropertySymbol , lsyntax , this , 0 ) ;
150- var right = MakeSubtractionExpression ( this , 1 ) ;
151-
152- CreateFromRangeEndpoint ( trapFile , lengthPropertySymbol , rsyntax , right , 0 ) ;
153- CreateFromRangeEndpoint ( trapFile , lengthPropertySymbol , lsyntax , right , 1 ) ;
154- SetExprArgument ( trapFile , left , right ) ;
155- break ;
156- }
157- case ( null , ExpressionSyntax rsyntax ) :
158- {
159- var left = Literal . CreateGenerated ( Context , this , 0 , Context . Compilation . GetSpecialType ( SpecialType . System_Int32 ) , 0 , Location ) ;
160- var right = CreateFromRangeEndpoint ( trapFile , lengthPropertySymbol , rsyntax , this , 1 ) ;
161- SetExprArgument ( trapFile , left , right ) ;
162- break ;
163- }
164- case ( ExpressionSyntax lsyntax , null ) :
165- {
166-
167- var left = CreateFromRangeEndpoint ( trapFile , lengthPropertySymbol , lsyntax , this , 0 ) ;
168- var right = MakeSubtractionExpression ( this , 1 ) ;
169- MakeLengthPropertyCall ( trapFile , lengthPropertySymbol , right , 0 ) ;
170- CreateFromRangeEndpoint ( trapFile , lengthPropertySymbol , lsyntax , right , 1 ) ;
171- SetExprArgument ( trapFile , left , right ) ;
172- break ;
173- }
174- case ( null , null ) :
175- {
176- var left = Literal . CreateGenerated ( Context , this , 0 , Context . Compilation . GetSpecialType ( SpecialType . System_Int32 ) , 0 , Location ) ;
177- var right = MakeLengthPropertyCall ( trapFile , lengthPropertySymbol , this , 1 ) ;
178- SetExprArgument ( trapFile , left , right ) ;
179- break ;
180- }
181- }
124+ var left = range . LeftOperand is ExpressionSyntax lsyntax
125+ ? MakeFromRangeEndpoint ( lsyntax , this , 0 )
126+ : MakeZeroLiteral ( this , 0 ) ;
127+
128+ var right = range . RightOperand is ExpressionSyntax rsyntax
129+ ? MakeFromRangeEndpoint ( rsyntax , this , 1 )
130+ : MakeZeroFromEndExpression ( this , 1 ) ;
182131
132+ SetExprArgument ( trapFile , left , right ) ;
183133 trapFile . expr_call ( this , Method . Create ( Context , slice ) ) ;
184134 }
185135
@@ -198,13 +148,12 @@ protected override void PopulateExpression(TextWriter trapFile)
198148 Create ( Context , qualifier , this , - 1 ) ;
199149
200150 var target = GetTargetSymbol ( ) ;
201- if ( target is IMethodSymbol method && IsSliceWithRange ( method , out var lengthPropertySymbol , out var range ) )
151+ if ( target is IMethodSymbol method && IsSliceWithRange ( method , out var range ) )
202152 {
203153 // When an indexer on a span or string is used in conjunction with a range expression, the compiler translates
204154 // this into a call to the "Slice" or "Substring" method.
205155 // In this case, we want to populate a slice/substring method call instead of an indexer access.
206- // E.g s[1..4] gets translated to s.Slice(1, 4 - 1) if s is a span.
207- PopulateSlice ( trapFile , lengthPropertySymbol , method , range ) ;
156+ PopulateSlice ( trapFile , method , range ) ;
208157 return ;
209158 }
210159
0 commit comments