Skip to content

Commit

Permalink
Fix single quote and space in F# pretty methods (#4969)
Browse files Browse the repository at this point in the history
  • Loading branch information
nohwnd committed Apr 9, 2024
1 parent cd4e4a4 commit 9ce6c03
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 19 deletions.
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

0 comments on commit 9ce6c03

Please sign in to comment.