Skip to content

C#: Support for record structs #7605

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 7 commits into from
Jan 19, 2022
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 @@ -111,6 +111,9 @@ public static void ExtractModifiers(Context cx, TextWriter trapFile, IEntity key
if (nt is null)
throw new InternalError(symbol, "Symbol kind is inconsistent with its type");

if (nt.IsRecord)
HasModifier(cx, trapFile, key, "record");

if (nt.TypeKind == TypeKind.Struct)
{
if (nt.IsReadOnly)
Expand Down
2 changes: 1 addition & 1 deletion csharp/ql/lib/semmle/code/cil/Types.qll
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class Class extends ValueOrRefType {
}

/** A `record`. */
class Record extends Class {
deprecated class Record extends Class {
Record() { this.isRecord() }
}

Expand Down
38 changes: 34 additions & 4 deletions csharp/ql/lib/semmle/code/csharp/Type.qll
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,8 @@ class ValueOrRefType extends DotNet::ValueOrRefType, Type, Attributable, @value_
nested_types(this, _, result)
}

override predicate isRecord() { this.hasModifier("record") }

override string toString() { result = Type.super.toString() }
}

Expand Down Expand Up @@ -449,6 +451,14 @@ class SimpleType extends ValueType, @simple_type {
override SystemNamespace getDeclaringNamespace() { any() }
}

/**
* A `record` like type.
* This can be either a `class` or a `struct`.
*/
class RecordType extends ValueOrRefType {
RecordType() { this.isRecord() }
}

/**
* The Boolean type, `bool`.
*/
Expand Down Expand Up @@ -711,6 +721,18 @@ class Struct extends ValueType, @struct_type {
override string getAPrimaryQlClass() { result = "Struct" }
}

/**
* A `record struct`, for example
* ```csharp
* record struct RS {
* ...
* }
* ```
*/
class RecordStruct extends RecordType, Struct {
override string getAPrimaryQlClass() { result = "RecordStruct" }
}

/**
* A reference type.
*
Expand Down Expand Up @@ -765,6 +787,16 @@ class Class extends RefType, @class_type {
override string getAPrimaryQlClass() { result = "Class" }
}

/**
* DEPRECATED: Use `RecordClass` instead.
*/
deprecated class Record extends Class {
Record() { this.isRecord() }

/** Gets the clone method of this record. */
RecordCloneMethod getCloneMethod() { result = this.getAMember() }
}

/**
* A `record`, for example
*
Expand All @@ -774,13 +806,11 @@ class Class extends RefType, @class_type {
* }
* ```
*/
class Record extends Class {
Record() { this.isRecord() }

class RecordClass extends RecordType, Class {
/** Gets the clone method of this record. */
RecordCloneMethod getCloneMethod() { result = this.getAMember() }

override string getAPrimaryQlClass() { result = "Record" }
override string getAPrimaryQlClass() { result = "RecordClass" }
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ module SummaryComponentStack {
class SummarizedCallable = Impl::Public::SummarizedCallable;

private predicate recordConstructorFlow(Constructor c, int i, Property p) {
c = any(Record r).getAMember() and
c = any(RecordType r).getAMember() and
exists(string name |
c.getParameter(i).getName() = name and
c.getDeclaringType().getAMember(name) = p
Expand Down
4 changes: 3 additions & 1 deletion csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll
Original file line number Diff line number Diff line change
Expand Up @@ -1175,7 +1175,9 @@ class WithExpr extends Expr, @with_expr {
Expr getExpr() { result = this.getChild(0) }

/** Gets the clone method of the `record` that is targetted by this `with` expression. */
RecordCloneMethod getCloneMethod() { result = this.getExpr().getType().(Record).getCloneMethod() }
RecordCloneMethod getCloneMethod() {
result = this.getExpr().getType().(RecordClass).getCloneMethod()
}

override string toString() { result = "... with { ... }" }

Expand Down

This file was deleted.

12 changes: 12 additions & 0 deletions csharp/ql/test/library-tests/csharp10/RecordTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

public record MyEntry(string Name, string Address)
{
sealed public override string ToString() => $"{Name} lives at {Address}";
};

public record class MyClassRecord(DateTime stuff) { }

public readonly record struct MyReadonlyRecordStruct(string Stuff) { }

public record struct MyRecordStruct(int Stuff) { }
Original file line number Diff line number Diff line change
@@ -1 +1 @@
| RecordTypeSealedToString.cs:5:35:5:42 | ToString |
| RecordTypes.cs:5:35:5:42 | ToString |
11 changes: 11 additions & 0 deletions csharp/ql/test/library-tests/csharp10/recordTypes.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
recordTypes
| RecordTypes.cs:3:1:6:2 | MyEntry |
| RecordTypes.cs:8:1:8:53 | MyClassRecord |
| RecordTypes.cs:10:1:10:70 | MyReadonlyRecordStruct |
| RecordTypes.cs:12:1:12:50 | MyRecordStruct |
recordStructs
| RecordTypes.cs:10:1:10:70 | MyReadonlyRecordStruct |
| RecordTypes.cs:12:1:12:50 | MyRecordStruct |
recordClass
| RecordTypes.cs:3:1:6:2 | MyEntry |
| RecordTypes.cs:8:1:8:53 | MyClassRecord |
7 changes: 7 additions & 0 deletions csharp/ql/test/library-tests/csharp10/recordTypes.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import csharp

query predicate recordTypes(RecordType rt) { any() }

query predicate recordStructs(RecordStruct rs) { any() }

query predicate recordClass(RecordClass r) { any() }
20 changes: 10 additions & 10 deletions csharp/ql/test/library-tests/csharp9/PrintAst.expected
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ ParenthesizedPattern.cs:
# 26| 0: [TypeMention] string
# 26| 2: [IntLiteral] 5
Record.cs:
# 4| [Record] Person
# 4| [RecordClass] Person
# 4| 11: [NEOperator] !=
#-----| 2: (Parameters)
# 4| 0: [Parameter] left
Expand Down Expand Up @@ -806,7 +806,7 @@ Record.cs:
# 9| 1: [TupleExpr] (..., ...)
# 9| 0: [ParameterAccess] access to parameter first
# 9| 1: [ParameterAccess] access to parameter last
# 12| [Record] Teacher
# 12| [RecordClass] Teacher
# 12| 12: [NEOperator] !=
#-----| 2: (Parameters)
# 12| 0: [Parameter] left
Expand Down Expand Up @@ -834,7 +834,7 @@ Record.cs:
# 17| 4: [AssignExpr] ... = ...
# 17| 0: [PropertyCall] access to property Subject
# 17| 1: [ParameterAccess] access to parameter sub
# 20| [Record] Student
# 20| [RecordClass] Student
# 20| 12: [NEOperator] !=
#-----| 2: (Parameters)
# 20| 0: [Parameter] left
Expand Down Expand Up @@ -862,7 +862,7 @@ Record.cs:
# 24| 4: [AssignExpr] ... = ...
# 24| 0: [PropertyCall] access to property Level
# 24| 1: [ParameterAccess] access to parameter level
# 27| [Record] Person1
# 27| [RecordClass] Person1
# 27| 12: [NEOperator] !=
#-----| 2: (Parameters)
# 27| 0: [Parameter] left
Expand All @@ -889,7 +889,7 @@ Record.cs:
# 27| 4: [Setter] set_LastName
#-----| 2: (Parameters)
# 27| 0: [Parameter] value
# 29| [Record] Teacher1
# 29| [RecordClass] Teacher1
# 29| 13: [NEOperator] !=
#-----| 2: (Parameters)
# 29| 0: [Parameter] left
Expand All @@ -913,7 +913,7 @@ Record.cs:
# 29| 4: [Setter] set_Subject
#-----| 2: (Parameters)
# 29| 0: [Parameter] value
# 32| [Record] Student1
# 32| [RecordClass] Student1
# 32| 13: [NEOperator] !=
#-----| 2: (Parameters)
# 32| 0: [Parameter] left
Expand All @@ -937,7 +937,7 @@ Record.cs:
# 32| 4: [Setter] set_Level
#-----| 2: (Parameters)
# 32| 0: [Parameter] value
# 35| [Record] Pet
# 35| [RecordClass] Pet
# 35| 12: [NEOperator] !=
#-----| 2: (Parameters)
# 35| 0: [Parameter] left
Expand All @@ -963,7 +963,7 @@ Record.cs:
# 38| -1: [TypeAccess] access to type Console
# 38| 0: [TypeMention] Console
# 38| 0: [StringLiteral] "Shredding furniture"
# 41| [Record] Dog
# 41| [RecordClass] Dog
# 41| 12: [NEOperator] !=
#-----| 2: (Parameters)
# 41| 0: [Parameter] left
Expand Down Expand Up @@ -1002,7 +1002,7 @@ Record.cs:
# 50| 0: [MethodCall] call to method ToString
# 50| -1: [LocalVariableAccess] access to local variable s
# 50| 1: [StringLiteral] " is a dog"
# 54| [Record] R1
# 54| [RecordClass] R1
# 54| 12: [NEOperator] !=
#-----| 2: (Parameters)
# 54| 0: [Parameter] left
Expand All @@ -1022,7 +1022,7 @@ Record.cs:
# 54| 4: [Setter] set_A
#-----| 2: (Parameters)
# 54| 0: [Parameter] value
# 56| [Record] R2
# 56| [RecordClass] R2
# 56| 13: [NEOperator] !=
#-----| 2: (Parameters)
# 56| 0: [Parameter] left
Expand Down
4 changes: 2 additions & 2 deletions csharp/ql/test/library-tests/csharp9/record.ql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import csharp

query predicate records(Record t, string i, RecordCloneMethod clone) {
query predicate records(RecordClass t, string i, RecordCloneMethod clone) {
t.getABaseInterface().toStringWithTypes() = i and
clone = t.getCloneMethod() and
t.fromSource()
Expand All @@ -10,7 +10,7 @@ private string getMemberName(Member m) {
result = m.getDeclaringType().getQualifiedName() + "." + m.toStringWithTypes()
}

query predicate members(Record t, string ms, string l) {
query predicate members(RecordClass t, string ms, string l) {
t.fromSource() and
exists(Member m | t.hasMember(m) |
ms = getMemberName(m) and
Expand Down
Loading