From 38283a38b5bb30e22267517b2db694180ff339de Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 20 Dec 2025 03:42:43 +0000
Subject: [PATCH 1/2] Initial plan
From b8d51af55a90e9093a8a811f88d9d08342a5d653 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 20 Dec 2025 03:50:42 +0000
Subject: [PATCH 2/2] Add tests for ref scoped modifier
Co-authored-by: JoeRobich <611219+JoeRobich@users.noreply.github.com>
---
grammars/csharp.tmLanguage | 18 ++++++++++------
grammars/csharp.tmLanguage.cson | 15 +++++++------
src/csharp.tmLanguage.yml | 24 +++++++++++----------
test/local.tests.ts | 38 +++++++++++++++++++++++++++++++++
test/method.tests.ts | 31 +++++++++++++++++++++++++++
5 files changed, 103 insertions(+), 23 deletions(-)
diff --git a/grammars/csharp.tmLanguage b/grammars/csharp.tmLanguage
index 9d66b73..985acac 100644
--- a/grammars/csharp.tmLanguage
+++ b/grammars/csharp.tmLanguage
@@ -4799,13 +4799,13 @@
begin
(?x)
(?:
- (?:(\bscoped)\s+(?:(\bref)\s+)?)? # scoped local
- (?:(\bref)\s+(?:(\breadonly)\s+)?)? # ref local
+ (?:(\bscoped)\s+(?:(\bref)\s+)?)? # scoped ref local
+ (?:(\bref)\s+(?:(\breadonly|scoped)\s+)?)? # ref readonly or ref scoped local
(\bvar\b)|
(?<type_name>
(?:
- (?:scoped\s+(?:ref\s+)?)? # scoped local
- (?:ref\s+(?:readonly\s+)?)? # ref local
+ (?:scoped\s+(?:ref\s+)?)? # scoped ref local
+ (?:ref\s+(?:(?:readonly|scoped)\s+)?)? # ref, optionally followed by readonly or scoped
(?:
(?:(?<identifier>@?[_[:alpha:]][_[:alnum:]]*)\s*\:\:\s*)? # alias-qualification
(?<name_and_type_args> # identifier + type arguments (if any)
@@ -4850,7 +4850,7 @@
4
name
- storage.modifier.readonly.cs
+ storage.modifier.$4.cs
5
@@ -7298,6 +7298,7 @@
match
(?x)
(?:(?:\b(scoped|ref|params|out|in|this)\b)\s+)?
+(?:(?:\b(ref|scoped|readonly)\b)\s+)?
(?<type_name>
(?:
(?:ref\s+)? # ref return
@@ -7330,6 +7331,11 @@
storage.modifier.$1.cs
2
+
+ name
+ storage.modifier.$2.cs
+
+ 3
patterns
@@ -7339,7 +7345,7 @@
- 7
+ 8
name
entity.name.variable.parameter.cs
diff --git a/grammars/csharp.tmLanguage.cson b/grammars/csharp.tmLanguage.cson
index 86263bf..24e4551 100644
--- a/grammars/csharp.tmLanguage.cson
+++ b/grammars/csharp.tmLanguage.cson
@@ -2931,13 +2931,13 @@ repository:
begin: '''
(?x)
(?:
- (?:(\\bscoped)\\s+(?:(\\bref)\\s+)?)? # scoped local
- (?:(\\bref)\\s+(?:(\\breadonly)\\s+)?)? # ref local
+ (?:(\\bscoped)\\s+(?:(\\bref)\\s+)?)? # scoped ref local
+ (?:(\\bref)\\s+(?:(\\breadonly|scoped)\\s+)?)? # ref readonly or ref scoped local
(\\bvar\\b)|
(?
(?:
- (?:scoped\\s+(?:ref\\s+)?)? # scoped local
- (?:ref\\s+(?:readonly\\s+)?)? # ref local
+ (?:scoped\\s+(?:ref\\s+)?)? # scoped ref local
+ (?:ref\\s+(?:(?:readonly|scoped)\\s+)?)? # ref, optionally followed by readonly or scoped
(?:
(?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification
(? # identifier + type arguments (if any)
@@ -2971,7 +2971,7 @@ repository:
"3":
name: "storage.modifier.ref.cs"
"4":
- name: "storage.modifier.readonly.cs"
+ name: "storage.modifier.$4.cs"
"5":
name: "storage.type.var.cs"
"6":
@@ -4401,6 +4401,7 @@ repository:
match: '''
(?x)
(?:(?:\\b(scoped|ref|params|out|in|this)\\b)\\s+)?
+ (?:(?:\\b(ref|scoped|readonly)\\b)\\s+)?
(?
(?:
(?:ref\\s+)? # ref return
@@ -4430,12 +4431,14 @@ repository:
"1":
name: "storage.modifier.$1.cs"
"2":
+ name: "storage.modifier.$2.cs"
+ "3":
patterns: [
{
include: "#type"
}
]
- "7":
+ "8":
name: "entity.name.variable.parameter.cs"
"argument-list":
begin: "\\("
diff --git a/src/csharp.tmLanguage.yml b/src/csharp.tmLanguage.yml
index d99b878..c161052 100644
--- a/src/csharp.tmLanguage.yml
+++ b/src/csharp.tmLanguage.yml
@@ -1739,13 +1739,13 @@ repository:
begin: |-
(?x)
(?:
- (?:(\bscoped)\s+(?:(\bref)\s+)?)? # scoped local
- (?:(\bref)\s+(?:(\breadonly)\s+)?)? # ref local
+ (?:(\bscoped)\s+(?:(\bref)\s+)?)? # scoped ref local
+ (?:(\bref)\s+(?:(\breadonly|scoped)\s+)?)? # ref readonly or ref scoped local
(\bvar\b)|
(?
(?:
- (?:scoped\s+(?:ref\s+)?)? # scoped local
- (?:ref\s+(?:readonly\s+)?)? # ref local
+ (?:scoped\s+(?:ref\s+)?)? # scoped ref local
+ (?:ref\s+(?:(?:readonly|scoped)\s+)?)? # ref, optionally followed by readonly or scoped
(?:
(?:(?@?[_[:alpha:]][_[:alnum:]]*)\s*\:\:\s*)? # alias-qualification
(? # identifier + type arguments (if any)
@@ -1774,7 +1774,7 @@ repository:
1: { name: storage.modifier.scoped.cs }
2: { name: storage.modifier.ref.cs }
3: { name: storage.modifier.ref.cs }
- 4: { name: storage.modifier.readonly.cs }
+ 4: { name: storage.modifier.$4.cs }
5: { name: storage.type.var.cs }
6:
patterns:
@@ -2835,6 +2835,7 @@ repository:
match: |-
(?x)
(?:(?:\b(scoped|ref|params|out|in|this)\b)\s+)?
+ (?:(?:\b(ref|scoped|readonly)\b)\s+)?
(?
(?:
(?:ref\s+)? # ref return
@@ -2861,14 +2862,15 @@ repository:
(\g)
captures:
1: { name: storage.modifier.$1.cs }
- 2:
+ 2: { name: storage.modifier.$2.cs }
+ 3:
patterns:
- include: '#type'
- # '3': ? is a sub-expression. It's final value is not considered.
- # '4': ? is a sub-expression. It's final value is not considered.
- # '5': ? is a sub-expression. It's final value is not considered.
- # '6': ? is a sub-expression. It's final value is not considered.
- 7: { name: entity.name.variable.parameter.cs }
+ # '4': ? is a sub-expression. It's final value is not considered.
+ # '5': ? is a sub-expression. It's final value is not considered.
+ # '6': ? is a sub-expression. It's final value is not considered.
+ # '7': ? is a sub-expression. It's final value is not considered.
+ 8: { name: entity.name.variable.parameter.cs }
argument-list:
begin: \(
diff --git a/test/local.tests.ts b/test/local.tests.ts
index 4077bf3..988c9e6 100644
--- a/test/local.tests.ts
+++ b/test/local.tests.ts
@@ -169,6 +169,44 @@ describe("Locals", () => {
]);
});
+ it("ref scoped local", async () => {
+ const input = Input.InMethod(`ref scoped Span x = stackalloc int[42];`);
+ const tokens = await tokenize(input);
+
+ tokens.should.deep.equal([
+ Token.Keyword.Modifier.Ref,
+ Token.Keyword.Modifier.Scoped,
+ Token.Type("Span"),
+ Token.Punctuation.TypeParameter.Begin,
+ Token.PrimitiveType.Int,
+ Token.Punctuation.TypeParameter.End,
+ Token.Identifier.LocalName("x"),
+ Token.Operator.Assignment,
+ Token.Operator.Expression.StackAlloc,
+ Token.PrimitiveType.Int,
+ Token.Punctuation.OpenBracket,
+ Token.Literal.Numeric.Decimal("42"),
+ Token.Punctuation.CloseBracket,
+ Token.Punctuation.Semicolon
+ ]);
+ });
+
+ it("ref scoped local var", async () => {
+ const input = Input.InMethod(`ref scoped var x = ref y;`);
+ const tokens = await tokenize(input);
+
+ tokens.should.deep.equal([
+ Token.Keyword.Modifier.Ref,
+ Token.Keyword.Modifier.Scoped,
+ Token.Keyword.Definition.Var,
+ Token.Identifier.LocalName("x"),
+ Token.Operator.Assignment,
+ Token.Keyword.Modifier.Ref,
+ Token.Variable.ReadWrite("y"),
+ Token.Punctuation.Semicolon
+ ]);
+ });
+
it("ref local", async () => {
const input = Input.InMethod(`ref int x;`);
const tokens = await tokenize(input);
diff --git a/test/method.tests.ts b/test/method.tests.ts
index edaba0b..b57f9f2 100644
--- a/test/method.tests.ts
+++ b/test/method.tests.ts
@@ -857,6 +857,37 @@ public static Span CreateSpan(scoped ref T reference, int length) {}
]);
});
+ it("ref scoped parameter in parameter list (#306)", async () => {
+ const input = Input.InClass(`
+public static Span CreateSpan(ref scoped T reference, int length) {}
+`);
+ const tokens = await tokenize(input);
+
+ tokens.should.deep.equal([
+ Token.Keyword.Modifier.Public,
+ Token.Keyword.Modifier.Static,
+ Token.Type("Span"),
+ Token.Punctuation.TypeParameter.Begin,
+ Token.Type("T"),
+ Token.Punctuation.TypeParameter.End,
+ Token.Identifier.MethodName("CreateSpan"),
+ Token.Punctuation.TypeParameter.Begin,
+ Token.Identifier.TypeParameterName("T"),
+ Token.Punctuation.TypeParameter.End,
+ Token.Punctuation.OpenParen,
+ Token.Keyword.Modifier.Ref,
+ Token.Keyword.Modifier.Scoped,
+ Token.Type("T"),
+ Token.Identifier.ParameterName("reference"),
+ Token.Punctuation.Comma,
+ Token.PrimitiveType.Int,
+ Token.Identifier.ParameterName("length"),
+ Token.Punctuation.CloseParen,
+ Token.Punctuation.OpenBrace,
+ Token.Punctuation.CloseBrace
+ ]);
+ });
+
it("expression body and type constraint (issue #74)", async () => {
const input = Input.InClass(`
T id1(T a) => a;