Skip to content

C# 11: Support for file scoped types. #12234

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ private static void ExtractNamedTypeModifiers(Context cx, TextWriter trapFile, I
if (nt.IsRecord)
HasModifier(cx, trapFile, key, Modifiers.Record);

if (nt.IsFileLocal)
HasModifier(cx, trapFile, key, Modifiers.File);

if (nt.TypeKind == TypeKind.Struct)
{
if (nt.IsReadOnly)
Expand All @@ -97,7 +100,11 @@ private static void ExtractNamedTypeModifiers(Context cx, TextWriter trapFile, I

public static void ExtractModifiers(Context cx, TextWriter trapFile, IEntity key, ISymbol symbol)
{
HasAccessibility(cx, trapFile, key, symbol.DeclaredAccessibility);
// A file scoped type has declared accessibility `internal` which we shouldn't extract.
// The file modifier is extracted as a source level modifier.
if (symbol.Kind != SymbolKind.NamedType || !((INamedTypeSymbol)symbol).IsFileLocal)
HasAccessibility(cx, trapFile, key, symbol.DeclaredAccessibility);

if (symbol.Kind == SymbolKind.ErrorType)
trapFile.has_modifiers(key, Modifier.Create(cx, Accessibility.Public));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ internal static class Modifiers
public const string Async = "async";
public const string Const = "const";
public const string Extern = "extern";
public const string File = "file";
public const string Internal = "internal";
public const string New = "new";
public const string Override = "override";
Expand Down
76 changes: 41 additions & 35 deletions csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,54 +286,60 @@ private static void BuildFunctionPointerTypeId(this IFunctionPointerTypeSymbol f
public static IEnumerable<IFieldSymbol?> GetTupleElementsMaybeNull(this INamedTypeSymbol type) =>
type.TupleElements;

private static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined, bool constructUnderlyingTupleType)
private static void BuildQualifierAndName(INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined)
{
if (!constructUnderlyingTupleType && named.IsTupleType)
if (named.ContainingType is not null)
{
trapFile.Write('(');
trapFile.BuildList(",", named.GetTupleElementsMaybeNull(),
(i, f) =>
{
if (f is null)
{
trapFile.Write($"null({i})");
}
else
{
trapFile.Write((f.CorrespondingTupleField ?? f).Name);
trapFile.Write(":");
f.Type.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false);
}
}
);
trapFile.Write(")");
return;
named.ContainingType.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false);
trapFile.Write('.');
}

void AddContaining()
else if (named.ContainingNamespace is not null)
{
if (named.ContainingType is not null)
{
named.ContainingType.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false);
trapFile.Write('.');
}
else if (named.ContainingNamespace is not null)
if (cx.ShouldAddAssemblyTrapPrefix && named.ContainingAssembly is not null)
BuildAssembly(named.ContainingAssembly, trapFile);
named.ContainingNamespace.BuildNamespace(cx, trapFile);
}

var name = named.IsFileLocal ? named.MetadataName : named.Name;
trapFile.Write(name);
}

private static void BuildTupleId(INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined)
{
trapFile.Write('(');
trapFile.BuildList(",", named.GetTupleElementsMaybeNull(),
(i, f) =>
{
if (cx.ShouldAddAssemblyTrapPrefix && named.ContainingAssembly is not null)
BuildAssembly(named.ContainingAssembly, trapFile);
named.ContainingNamespace.BuildNamespace(cx, trapFile);
if (f is null)
{
trapFile.Write($"null({i})");
}
else
{
trapFile.Write((f.CorrespondingTupleField ?? f).Name);
trapFile.Write(":");
f.Type.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false);
}
}
);
trapFile.Write(")");
}

private static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined, bool constructUnderlyingTupleType)
{
if (!constructUnderlyingTupleType && named.IsTupleType)
{
BuildTupleId(named, cx, trapFile, symbolBeingDefined);
return;
}

if (named.TypeParameters.IsEmpty)
{
AddContaining();
trapFile.Write(named.Name);
BuildQualifierAndName(named, cx, trapFile, symbolBeingDefined);
}
else if (named.IsReallyUnbound())
{
AddContaining();
trapFile.Write(named.Name);
BuildQualifierAndName(named, cx, trapFile, symbolBeingDefined);
trapFile.Write("`");
trapFile.Write(named.TypeParameters.Length);
}
Expand Down
4 changes: 4 additions & 0 deletions csharp/ql/lib/change-notes/2023-02-17-filescopedtypes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* C# 11: Added extractor and library support for `file` scoped types.
5 changes: 5 additions & 0 deletions csharp/ql/lib/semmle/code/csharp/Member.qll
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ class Modifiable extends Declaration, @modifiable {
/** Holds if this declaration has the modifier `required`. */
predicate isRequired() { this.hasModifier("required") }

/** Holds if this declaration is `file` local. */
predicate isFile() { this.hasModifier("file") }

/** Holds if this declaration is `unsafe`. */
predicate isUnsafe() {
this.hasModifier("unsafe") or
Expand Down Expand Up @@ -183,6 +186,8 @@ class Member extends DotNet::Member, Modifiable, @member {
override predicate isStatic() { Modifiable.super.isStatic() }

override predicate isRequired() { Modifiable.super.isRequired() }

override predicate isFile() { Modifiable.super.isFile() }
}

private class TOverridable = @virtualizable or @callable_accessor;
Expand Down
3 changes: 3 additions & 0 deletions csharp/ql/lib/semmle/code/dotnet/Declaration.qll
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ class Member extends Declaration, @dotnet_member {
/** Holds if this member is declared `required`. */
predicate isRequired() { none() }

/** Holds if this member is declared `file` local. */
predicate isFile() { none() }

/**
* Holds if this member has name `name` and is defined in type `type`
* with namespace `namespace`.
Expand Down
25 changes: 25 additions & 0 deletions csharp/ql/test/library-tests/csharp11/FileScoped1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
file interface I1 { }

file interface I2 { }

file class C1 : I1 { }

public class C2 { }

public class C3 : I2 { }

file interface IC { }

file class C4<T> { }

file class C5<S> : C4<S> { }

file struct S1 { }

file enum E1 { }

file delegate void D1();

file record R1 { }

file record struct RS1 { }
23 changes: 23 additions & 0 deletions csharp/ql/test/library-tests/csharp11/FileScoped2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
file interface I1 { }

public interface I2 { }

file class C1 { }

file class C2 : I2 { }

file class IC { }

file class C4<T> { }

file class C5<S> : C4<S> { }

file struct S1 { }

file enum E1 { }

file delegate void D1();

file record R1 { }

file record struct RS1 { }
7 changes: 7 additions & 0 deletions csharp/ql/test/library-tests/csharp11/FileScoped3.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace TestFileScoped;

file interface I10 { }

file class C10 { }

public class C11 : I10 { }
7 changes: 7 additions & 0 deletions csharp/ql/test/library-tests/csharp11/FileScoped4.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace TestFileScoped;

public interface I10 { }

file class C10 { }

file class C11 : I10 { }
97 changes: 97 additions & 0 deletions csharp/ql/test/library-tests/csharp11/PrintAst.expected
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,103 @@ CheckedOperators.cs:
# 55| 0: [TypeMention] short
# 55| 1: [PropertyCall] access to property Value
# 55| -1: [ParameterAccess] access to parameter n
FileScoped1.cs:
# 1| [Interface] I1
# 3| [Interface] I2
# 5| [Class] C1
#-----| 3: (Base types)
# 5| 1: [TypeMention] I1
# 7| [Class] C2
# 9| [Class] C3
#-----| 3: (Base types)
# 9| 1: [TypeMention] I2
# 11| [Interface] IC
# 13| [Class] C4<>
#-----| 1: (Type parameters)
# 13| 0: [TypeParameter] T
# 15| [Class] C5<>
#-----| 1: (Type parameters)
# 15| 0: [TypeParameter] S
#-----| 3: (Base types)
# 15| 0: [TypeMention] C4<S>
# 15| 1: [TypeMention] S
# 17| [Struct] S1
# 19| [Enum] E1
# 21| [DelegateType] D1
# 23| [RecordClass] R1
# 23| 12: [NEOperator] !=
#-----| 2: (Parameters)
# 23| 0: [Parameter] left
# 23| 1: [Parameter] right
# 23| 13: [EQOperator] ==
#-----| 2: (Parameters)
# 23| 0: [Parameter] left
# 23| 1: [Parameter] right
# 23| 14: [Property] EqualityContract
# 23| 3: [Getter] get_EqualityContract
# 25| [RecordStruct] RS1
# 25| 10: [NEOperator] !=
#-----| 2: (Parameters)
# 25| 0: [Parameter] left
# 25| 1: [Parameter] right
# 25| 11: [EQOperator] ==
#-----| 2: (Parameters)
# 25| 0: [Parameter] left
# 25| 1: [Parameter] right
FileScoped2.cs:
# 1| [Interface] I1
# 3| [Interface] I2
# 5| [Class] C1
# 7| [Class] C2
#-----| 3: (Base types)
# 7| 1: [TypeMention] I2
# 9| [Class] IC
# 11| [Class] C4<>
#-----| 1: (Type parameters)
# 11| 0: [TypeParameter] T
# 13| [Class] C5<>
#-----| 1: (Type parameters)
# 13| 0: [TypeParameter] S
#-----| 3: (Base types)
# 13| 0: [TypeMention] C4<S>
# 13| 1: [TypeMention] S
# 15| [Struct] S1
# 17| [Enum] E1
# 19| [DelegateType] D1
# 21| [RecordClass] R1
# 21| 12: [NEOperator] !=
#-----| 2: (Parameters)
# 21| 0: [Parameter] left
# 21| 1: [Parameter] right
# 21| 13: [EQOperator] ==
#-----| 2: (Parameters)
# 21| 0: [Parameter] left
# 21| 1: [Parameter] right
# 21| 14: [Property] EqualityContract
# 21| 3: [Getter] get_EqualityContract
# 23| [RecordStruct] RS1
# 23| 10: [NEOperator] !=
#-----| 2: (Parameters)
# 23| 0: [Parameter] left
# 23| 1: [Parameter] right
# 23| 11: [EQOperator] ==
#-----| 2: (Parameters)
# 23| 0: [Parameter] left
# 23| 1: [Parameter] right
FileScoped3.cs:
# 1| [NamespaceDeclaration] namespace ... { ... }
# 3| 1: [Interface] I10
# 5| 2: [Class] C10
# 7| 3: [Class] C11
#-----| 3: (Base types)
# 7| 1: [TypeMention] I10
FileScoped4.cs:
# 1| [NamespaceDeclaration] namespace ... { ... }
# 3| 1: [Interface] I10
# 5| 2: [Class] C10
# 7| 3: [Class] C11
#-----| 3: (Base types)
# 7| 1: [TypeMention] I10
GenericAttribute.cs:
# 3| [GenericAssemblyAttribute] [assembly: MyGeneric<Int32>(...)]
# 3| 0: [TypeMention] MyGenericAttribute<int>
Expand Down
Loading