Skip to content

Commit 7ed3d3f

Browse files
authored
Merge pull request #21065 from michaelnebel/csharp/implicitspanconversions
C# 14: Implicit span conversions.
2 parents 503a1b5 + 8fe31a1 commit 7ed3d3f

File tree

5 files changed

+190
-0
lines changed

5 files changed

+190
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* C# 14: Support for *implicit* span conversions in the QL library.

csharp/ql/lib/semmle/code/csharp/Conversion.qll

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ private module Cached {
2828
*
2929
* - Identity conversions
3030
* - Implicit numeric conversions
31+
* - Implicit span conversions
3132
* - Implicit nullable conversions
3233
* - Implicit reference conversions
3334
* - Boxing conversions
@@ -38,6 +39,8 @@ private module Cached {
3839
or
3940
convNumeric(fromType, toType)
4041
or
42+
convSpan(fromType, toType)
43+
or
4144
convNullableType(fromType, toType)
4245
or
4346
convRefTypeNonNull(fromType, toType)
@@ -81,6 +84,7 @@ private predicate implicitConversionNonNull(Type fromType, Type toType) {
8184
*
8285
* - Identity conversions
8386
* - Implicit numeric conversions
87+
* - Implicit span conversions
8488
* - Implicit nullable conversions
8589
* - Implicit reference conversions
8690
* - Boxing conversions
@@ -491,6 +495,51 @@ private predicate convNumericChar(SimpleType toType) {
491495

492496
private predicate convNumericFloat(SimpleType toType) { toType instanceof DoubleType }
493497

498+
private class SpanType extends GenericType {
499+
SpanType() { this.getUnboundGeneric() instanceof SystemSpanStruct }
500+
501+
Type getElementType() { result = this.getTypeArgument(0) }
502+
}
503+
504+
private class ReadOnlySpanType extends GenericType {
505+
ReadOnlySpanType() { this.getUnboundGeneric() instanceof SystemReadOnlySpanStruct }
506+
507+
Type getElementType() { result = this.getTypeArgument(0) }
508+
}
509+
510+
private class SimpleArrayType extends ArrayType {
511+
SimpleArrayType() {
512+
this.getRank() = 1 and
513+
this.getDimension() = 1
514+
}
515+
}
516+
517+
/**
518+
* INTERNAL: Do not use.
519+
*
520+
* Holds if there is an implicit span conversion from `fromType` to `toType`.
521+
*
522+
* 10.2.1: Implicit span conversions (added in C# 14).
523+
* [Documentation](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-14.0/first-class-span-types#span-conversions)
524+
*/
525+
predicate convSpan(Type fromType, Type toType) {
526+
fromType.(SimpleArrayType).getElementType() = toType.(SpanType).getElementType()
527+
or
528+
exists(Type fromElementType, Type toElementType |
529+
(
530+
fromElementType = fromType.(SimpleArrayType).getElementType() or
531+
fromElementType = fromType.(SpanType).getElementType() or
532+
fromElementType = fromType.(ReadOnlySpanType).getElementType()
533+
) and
534+
toElementType = toType.(ReadOnlySpanType).getElementType()
535+
|
536+
convRefTypeNonNull(fromElementType, toElementType)
537+
)
538+
or
539+
fromType instanceof SystemStringClass and
540+
toType.(ReadOnlySpanType).getElementType() instanceof CharType
541+
}
542+
494543
/**
495544
* INTERNAL: Do not use.
496545
*
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
public interface CovariantInterface<out T> { }
5+
6+
public interface ContravariantInterface<in T> { }
7+
8+
public interface InvariantInterface<T> { }
9+
10+
public interface MixedInterface<out T1, in T2> { }
11+
12+
public class Base { }
13+
14+
public class Derived : Base { }
15+
16+
public class C
17+
{
18+
public void M()
19+
{
20+
string[] stringArray = [];
21+
string[][] stringArrayArray = [];
22+
string[,] stringArray2D = new string[0, 0];
23+
24+
Span<string> stringSpan = stringArray; // string[] -> Span<string>;
25+
26+
// Assignments are included to illustrate that it compiles.
27+
// Only the use of the types matter in terms of test output.
28+
// Covariant conversions to ReadOnlySpan
29+
ReadOnlySpan<CovariantInterface<Base>> covariantInterfaceBaseReadOnlySpan;
30+
ReadOnlySpan<CovariantInterface<Derived>> covariantInterfaceDerivedReadOnlySpan = default;
31+
Span<CovariantInterface<Derived>> covariantInterfaceDerivedSpan = default;
32+
CovariantInterface<Derived>[] covariantInterfaceDerivedArray = [];
33+
covariantInterfaceBaseReadOnlySpan = covariantInterfaceDerivedReadOnlySpan; // ReadOnlySpan<CovariantInterface<Derived>> -> ReadOnlySpan<CovariantInterface<Base>>
34+
covariantInterfaceBaseReadOnlySpan = covariantInterfaceDerivedSpan; // Span<CovariantInterface<Derived>> -> ReadOnlySpan<CovariantInterface<Base>>
35+
covariantInterfaceBaseReadOnlySpan = covariantInterfaceDerivedArray; // CovariantInterface<Derived>[] -> ReadOnlySpan<CovariantInterface<Base>>
36+
37+
// Identify conversions to ReadOnlySpan
38+
ReadOnlySpan<string> stringReadOnlySpan;
39+
stringReadOnlySpan = stringSpan; // Span<string> -> ReadOnlySpan<string>;
40+
stringReadOnlySpan = stringArray; // string[] -> ReadOnlySpan<string>;
41+
42+
// Contravariant conversions to ReadOnlySpan
43+
ReadOnlySpan<ContravariantInterface<Derived>> contravariantInterfaceDerivedReadOnlySpan;
44+
ReadOnlySpan<ContravariantInterface<Base>> contravariantInterfaceBaseReadOnlySpan = default;
45+
Span<ContravariantInterface<Base>> contravariantInterfaceBaseSpan = default;
46+
ContravariantInterface<Base>[] contravariantInterfaceBaseArray = [];
47+
contravariantInterfaceDerivedReadOnlySpan = contravariantInterfaceBaseReadOnlySpan; // ReadOnlySpan<ContravariantInterface<Base>> -> ReadOnlySpan<ContravariantInterface<Derived>>
48+
contravariantInterfaceDerivedReadOnlySpan = contravariantInterfaceBaseSpan; // Span<ContravariantInterface<Base>> -> ReadOnlySpan<ContravariantInterface<Derived>>
49+
contravariantInterfaceDerivedReadOnlySpan = contravariantInterfaceBaseArray; // ContravariantInterface<Base>[] -> ReadOnlySpan<ContravariantInterface<Derived>>
50+
51+
// Mixed variance conversions to ReadOnlySpan
52+
ReadOnlySpan<MixedInterface<Base, Derived>> mixedInterfaceBaseReadOnlySpan;
53+
ReadOnlySpan<MixedInterface<Derived, Base>> mixedInterfaceDerivedReadOnlySpan = default;
54+
Span<MixedInterface<Derived, Base>> mixedInterfaceDerivedSpan = default;
55+
MixedInterface<Derived, Base>[] mixedInterfaceDerivedArray = [];
56+
mixedInterfaceBaseReadOnlySpan = mixedInterfaceDerivedReadOnlySpan; // ReadOnlySpan<MixedInterface<Derived, Base>> -> ReadOnlySpan<MixedInterface<Base, Derived>>
57+
mixedInterfaceBaseReadOnlySpan = mixedInterfaceDerivedSpan; // Span<MixedInterface<Derived, Base>> -> ReadOnlySpan<MixedInterface<Base, Derived>>
58+
mixedInterfaceBaseReadOnlySpan = mixedInterfaceDerivedArray; // MixedInterface<Derived, Base>[] -> ReadOnlySpan<MixedInterface<Base, Derived>>
59+
60+
// Convert string to ReadOnlySpan<char>
61+
string s = "";
62+
ReadOnlySpan<char> charReadOnlySpan = s; // string -> ReadOnlySpan<char>
63+
64+
// Various ref type conversions
65+
Derived[] derivedArray = [];
66+
ReadOnlySpan<Base> baseReadOnlySpan;
67+
baseReadOnlySpan = derivedArray; // Derived[] -> ReadOnlySpan<Base>
68+
69+
ReadOnlySpan<object> objectReadOnlySpan;
70+
objectReadOnlySpan = stringArray; // string[] -> ReadOnlySpan<object>
71+
72+
byte[][] byteByteArray = [];
73+
objectReadOnlySpan = byteByteArray; // byte[][] -> ReadOnlySpan<object>
74+
75+
// No conversion possible except for identity.
76+
ReadOnlySpan<InvariantInterface<Base>> invariantInterfaceBaseReadOnlySpan;
77+
ReadOnlySpan<InvariantInterface<Derived>> invariantInterfaceDerivedReadOnlySpan;
78+
Span<InvariantInterface<Derived>> invariantInterfaceDerivedSpan;
79+
InvariantInterface<Derived>[] invariantInterfaceDerivedArray;
80+
}
81+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
| ContravariantInterface<Base>[] | ReadOnlySpan<ContravariantInterface<Base>> |
2+
| ContravariantInterface<Base>[] | ReadOnlySpan<ContravariantInterface<Derived>> |
3+
| ContravariantInterface<Base>[] | ReadOnlySpan<object> |
4+
| ContravariantInterface<Base>[] | Span<ContravariantInterface<Base>> |
5+
| CovariantInterface<Derived>[] | ReadOnlySpan<CovariantInterface<Base>> |
6+
| CovariantInterface<Derived>[] | ReadOnlySpan<CovariantInterface<Derived>> |
7+
| CovariantInterface<Derived>[] | ReadOnlySpan<object> |
8+
| CovariantInterface<Derived>[] | Span<CovariantInterface<Derived>> |
9+
| Derived[] | ReadOnlySpan<Base> |
10+
| Derived[] | ReadOnlySpan<object> |
11+
| InvariantInterface<Derived>[] | ReadOnlySpan<InvariantInterface<Derived>> |
12+
| InvariantInterface<Derived>[] | ReadOnlySpan<object> |
13+
| InvariantInterface<Derived>[] | Span<InvariantInterface<Derived>> |
14+
| MixedInterface<Derived,Base>[] | ReadOnlySpan<MixedInterface<Base, Derived>> |
15+
| MixedInterface<Derived,Base>[] | ReadOnlySpan<MixedInterface<Derived, Base>> |
16+
| MixedInterface<Derived,Base>[] | ReadOnlySpan<object> |
17+
| MixedInterface<Derived,Base>[] | Span<MixedInterface<Derived, Base>> |
18+
| ReadOnlySpan<Base> | ReadOnlySpan<object> |
19+
| ReadOnlySpan<ContravariantInterface<Base>> | ReadOnlySpan<ContravariantInterface<Derived>> |
20+
| ReadOnlySpan<ContravariantInterface<Base>> | ReadOnlySpan<object> |
21+
| ReadOnlySpan<ContravariantInterface<Derived>> | ReadOnlySpan<object> |
22+
| ReadOnlySpan<CovariantInterface<Base>> | ReadOnlySpan<object> |
23+
| ReadOnlySpan<CovariantInterface<Derived>> | ReadOnlySpan<CovariantInterface<Base>> |
24+
| ReadOnlySpan<CovariantInterface<Derived>> | ReadOnlySpan<object> |
25+
| ReadOnlySpan<InvariantInterface<Base>> | ReadOnlySpan<object> |
26+
| ReadOnlySpan<InvariantInterface<Derived>> | ReadOnlySpan<object> |
27+
| ReadOnlySpan<MixedInterface<Base, Derived>> | ReadOnlySpan<object> |
28+
| ReadOnlySpan<MixedInterface<Derived, Base>> | ReadOnlySpan<MixedInterface<Base, Derived>> |
29+
| ReadOnlySpan<MixedInterface<Derived, Base>> | ReadOnlySpan<object> |
30+
| ReadOnlySpan<string> | ReadOnlySpan<object> |
31+
| Span<ContravariantInterface<Base>> | ReadOnlySpan<ContravariantInterface<Base>> |
32+
| Span<ContravariantInterface<Base>> | ReadOnlySpan<ContravariantInterface<Derived>> |
33+
| Span<ContravariantInterface<Base>> | ReadOnlySpan<object> |
34+
| Span<CovariantInterface<Derived>> | ReadOnlySpan<CovariantInterface<Base>> |
35+
| Span<CovariantInterface<Derived>> | ReadOnlySpan<CovariantInterface<Derived>> |
36+
| Span<CovariantInterface<Derived>> | ReadOnlySpan<object> |
37+
| Span<InvariantInterface<Derived>> | ReadOnlySpan<InvariantInterface<Derived>> |
38+
| Span<InvariantInterface<Derived>> | ReadOnlySpan<object> |
39+
| Span<MixedInterface<Derived, Base>> | ReadOnlySpan<MixedInterface<Base, Derived>> |
40+
| Span<MixedInterface<Derived, Base>> | ReadOnlySpan<MixedInterface<Derived, Base>> |
41+
| Span<MixedInterface<Derived, Base>> | ReadOnlySpan<object> |
42+
| Span<string> | ReadOnlySpan<object> |
43+
| Span<string> | ReadOnlySpan<string> |
44+
| String[] | ReadOnlySpan<object> |
45+
| String[] | ReadOnlySpan<string> |
46+
| String[] | Span<string> |
47+
| string | ReadOnlySpan<char> |
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import semmle.code.csharp.Conversion
2+
3+
private class InterestingType extends Type {
4+
InterestingType() { exists(LocalVariable lv | lv.getType() = this) }
5+
}
6+
7+
from InterestingType sub, InterestingType sup
8+
where convSpan(sub, sup) and sub != sup
9+
select sub.toStringWithTypes() as s1, sup.toStringWithTypes() as s2 order by s1, s2

0 commit comments

Comments
 (0)