Skip to content
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

Fix single quote and space in F# pretty methods #4969

Merged
merged 1 commit into from
Apr 9, 2024
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 @@ -82,12 +82,21 @@ private static int ParseMethodName(string managedMethodName, int start, out stri
{
var i = start;
var quoted = false;
char? previousChar = null;
// Consume all characters that are in single quotes as is. Because F# methods wrapped in `` can have any text, like ``method name``.
// and will be emitted into CIL as 'method name'.
// Make sure you ignore \', because that is how F# will escape ' if it appears in the method name.
for (; i < managedMethodName.Length; i++)
{
if ((i - 1) > 0)
{
previousChar = managedMethodName[i - 1];
}

var c = managedMethodName[i];
if (c == '\'' || quoted)
if ((c == '\'' && previousChar != '\\') || quoted)
{
quoted = c == '\'' ? !quoted : quoted;
quoted = (c == '\'' && previousChar != '\\') ? !quoted : quoted;
continue;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Linq;
using System.Reflection;

using Microsoft.TestPlatform.AcceptanceTests;
Expand All @@ -22,13 +24,24 @@ public void VerifyThatInvalidIdentifierNamesAreParsed()
var assembly = Assembly.LoadFrom(asset);
var types = assembly.GetTypes();

foreach (var type in types)
// Some methods can be implemented directly on the module, and are not in any class. This is how we get them.
var modules = assembly.GetModules().Select(m => new { Module = m, Type = m.GetType() });
var moduleTypes = modules.Select(m =>
{
var methods = type.GetMethods();
var runtimeTypeProperty = m.Type.GetProperty("RuntimeType", BindingFlags.Instance | BindingFlags.NonPublic);
var moduleType = (Type?)runtimeTypeProperty!.GetValue(m.Module, null)!;

return moduleType;
});

foreach (var type in types.Concat(moduleTypes).Where(t => t != null))
{
var methods = type!.GetMethods();

foreach (var method in methods)
{
if (method.DeclaringType != type) continue;
// Module methods have null Declaring type.
if (method.DeclaringType != null && method.DeclaringType != type) continue;

ManagedNameHelper.GetManagedName(method, out var typeName, out var methodName);
var methodInfo = ManagedNameHelper.GetMethod(assembly, typeName, methodName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ void AssertParse(string expectedMethod, int expectedArity, string[] expectedPara
AssertParse("Method", 1, new string[] { "B", "List<B>" }, "Method`1(B,List<B>)");
AssertParse("Method", 0, new string[] { "B[]" }, "Method(B[])");
AssertParse("Method", 0, new string[] { "A[,]", "B[,,][]" }, "Method(A[,],B[,,][])");

// An F# method that would look like this in code: member _.``method that does not pass`` () =
// And like this in CIL: instance void 'method that does not pass' () cil managed
Assert.AreEqual(("Method that does not pass", 0, null), Parse("'Method that does not pass'()"));

// An F# method that would look like this in code: member _.``method that doesn't pass`` () =
// And like this in CIL: instance void 'method that doesn\'t pass' () cil managed
// Notice the ' escaped by \.
Assert.AreEqual(("Method that doesn't pass", 0, null), Parse(@"'Method that doesn\'t pass'()"));
}

[TestMethod]
Expand Down
47 changes: 33 additions & 14 deletions test/TestAssets/CILProject/OrphanMethod.il
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,43 @@
.assembly extern mscorlib {}
.assembly extern System.Core {}

.method static public int32 Add(int32 x, int32 y) cil managed
.class public CleanNamespaceName.ClassName
{
.maxstack 2
.method static public int32 Add(int32 x, int32 y) cil managed
{
.maxstack 2

ldarg.0
ldarg.1
add
ret
}
ldarg.0
ldarg.1
add
ret
}

.method static public int32 'An awesome method to add 𝓧➕𝓨'(int32 '𝓧', int32 '𝓨') cil managed
{
.maxstack 2
.method static public int32 'An awesome method to add 𝓧➕𝓨'(int32 '𝓧', int32 '𝓨') cil managed
{
.maxstack 2

ldarg.0
ldarg.1
add
ret
}

// has ' and no spaces, e.g. coming from F# method ``don't``
.method static public void 'don\'t'() cil managed
{
.maxstack 1

ret
}

ldarg.0
ldarg.1
add
ret
// has ' and a space, e.g. coming from F# method ``don't pass``
.method static public void 'don\'t pass'() cil managed
{
.maxstack 1

ret
}
}

.class public CleanNamespaceName.SecondLevel.'𝐌𝐲 𝗮𝘄𝗲𝘀𝗼𝗺𝗲 𝘤𝘭𝘢𝘴𝘴 𝘢𝘯 𝒊𝒏𝒂𝒄𝒄𝒆𝒔𝒔𝒊𝒃𝒍𝒆 𝙣𝙖𝙢𝙚 😭'
Expand Down