Skip to content

Commit b09a0ef

Browse files
committed
Add support for searching for alternate invocees for externs
For certain C# methods, Udon exposes the method via a different type than Roslyn considers it to be defined on. One example is `System.Type.Name`, which is actually defined in `System.Reflection.MemberInfo`, but exposed via `System.Type.Name`. This change allows U# to locate these alternate extern symbols and use them transparently. We move the logic for checking whether symbols are exposed to udon down into BoundExternMethodInvocation, then teach this class to search declared interfaces and superclasses of the _invocation_ target (rather than the symbol's containing type) for defined externs. Note that we assume that any exposed extern will match if we find it via a constructed symbol here.
1 parent f8a0613 commit b09a0ef

8 files changed

+4540
-24
lines changed

Assets/UdonSharp/Editor/Compiler/Binder/BoundNodes/BoundArrayCreationExpression.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public ArrayConstructorExtern(AbstractPhaseContext context, TypeSymbol arrayType
2727
private BoundExpression[] Initializers { get; }
2828

2929
public BoundArrayCreationExpression(SyntaxNode node, AbstractPhaseContext context, TypeSymbol arrayType, BoundExpression[] rankSizes, BoundExpression[] initializers)
30-
: base(node, new ArrayConstructorExtern(context, arrayType), null, rankSizes)
30+
: base(node, context, new ArrayConstructorExtern(context, arrayType), null, rankSizes)
3131
{
3232
ArrayType = arrayType;
3333
Initializers = initializers;

Assets/UdonSharp/Editor/Compiler/Binder/BoundNodes/BoundExternMethodInvocation.cs

+52-2
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,67 @@
11

22
using System;
3+
using System.Collections.Generic;
34
using System.Linq;
45
using Microsoft.CodeAnalysis;
56
using UdonSharp.Compiler.Emit;
67
using UdonSharp.Compiler.Symbols;
8+
using UdonSharp.Compiler.Udon;
9+
using UdonSharp.Core;
10+
using UdonSharp.Localization;
711

812
namespace UdonSharp.Compiler.Binder
913
{
1014
internal class BoundExternInvocation : BoundInvocationExpression
1115
{
12-
public BoundExternInvocation(SyntaxNode node, MethodSymbol method, BoundExpression instanceExpression, BoundExpression[] parameterExpressions)
16+
private ExternMethodSymbol externMethodSymbol;
17+
18+
public BoundExternInvocation(SyntaxNode node, AbstractPhaseContext context, MethodSymbol method, BoundExpression instanceExpression, BoundExpression[] parameterExpressions)
1319
: base(node, method, instanceExpression, parameterExpressions)
1420
{
21+
externMethodSymbol = (ExternMethodSymbol)method;
22+
if (!CompilerUdonInterface.IsExposedToUdon(externMethodSymbol.ExternSignature))
23+
{
24+
externMethodSymbol = FindAlternateInvocation(context, method, instanceExpression, parameterExpressions);
25+
if (externMethodSymbol == null)
26+
{
27+
throw new NotExposedException(LocStr.CE_UdonMethodNotExposed, node, $"{method.RoslynSymbol?.ToDisplayString() ?? method.ToString()}, sig: {((ExternMethodSymbol)method).ExternSignature}");
28+
}
29+
}
30+
}
31+
32+
private ExternMethodSymbol FindAlternateInvocation(AbstractPhaseContext context, MethodSymbol originalSymbol, BoundExpression instanceExpression, BoundExpression[] parameterExpressions)
33+
{
34+
if (originalSymbol.IsStatic || originalSymbol.IsConstructor) return null;
35+
36+
List<TypeSymbol> candidates = new List<TypeSymbol>();
37+
FindCandidateInvocationTypes(context, candidates, instanceExpression.ValueType);
38+
39+
TypeSymbol[] paramTypes = parameterExpressions.Select(ex => ex.ValueType).ToArray();
40+
41+
foreach (var candidate in candidates)
42+
{
43+
ExternMethodSymbol externMethodSymbol = new ExternSynthesizedMethodSymbol(context, originalSymbol.Name, candidate, paramTypes, originalSymbol.ReturnType, false, false);
44+
if (CompilerUdonInterface.IsExposedToUdon(externMethodSymbol.ExternSignature))
45+
{
46+
return externMethodSymbol;
47+
}
48+
}
49+
50+
return null;
51+
}
52+
53+
void FindCandidateInvocationTypes(AbstractPhaseContext context, List<TypeSymbol> candidates, TypeSymbol ty)
54+
{
55+
foreach (var intf in ty.RoslynSymbol.AllInterfaces)
56+
{
57+
candidates.Add(context.GetTypeSymbol(intf));
58+
}
59+
60+
while (ty != null)
61+
{
62+
candidates.Add(ty);
63+
ty = ty.BaseType;
64+
}
1565
}
1666

1767
public override Value EmitValue(EmitContext context)
@@ -83,7 +133,7 @@ public override Value EmitValue(EmitContext context)
83133
module.AddPush(returnValue);
84134
}
85135

86-
module.AddExtern((ExternMethodSymbol) Method);
136+
module.AddExtern(externMethodSymbol);
87137

88138
if (recursiveValues != null)
89139
PopRecursiveValues(recursiveValues, context);

Assets/UdonSharp/Editor/Compiler/Binder/BoundNodes/BoundInvocationExpression.cs

+12-21
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ private static bool TryCreateInstantiationInvocation(AbstractPhaseContext contex
164164
switch (symbol.Name)
165165
{
166166
case "Instantiate_Extern" when symbol.ContainingType == context.GetTypeSymbol(typeof(InstantiationShim)):
167-
createdInvocation = new BoundExternInvocation(node,
167+
createdInvocation = new BoundExternInvocation(node, context,
168168
new ExternSynthesizedMethodSymbol(context,
169169
"VRCInstantiate.__Instantiate__UnityEngineGameObject__UnityEngineGameObject",
170170
parameterExpressions.Select(e => e.ValueType).ToArray(),
@@ -213,7 +213,7 @@ private static bool TryCreateSetProgramVariableInvocation(AbstractPhaseContext c
213213
.GetMembers<MethodSymbol>("SetProgramVariable", context)
214214
.First(e => !e.RoslynSymbol.IsGenericMethod);
215215

216-
createdInvocation = new BoundExternInvocation(node, setProgramVarObjMethod, instanceExpression,
216+
createdInvocation = new BoundExternInvocation(node, context, setProgramVarObjMethod, instanceExpression,
217217
parameterExpressions);
218218
return true;
219219
}
@@ -233,7 +233,7 @@ private static bool TryCreateArrayMethodInvocation(AbstractPhaseContext context,
233233
.GetMembers<MethodSymbol>(symbol.Name, context)
234234
.First(e => !e.RoslynSymbol.IsGenericMethod && e.Parameters.Length == symbol.Parameters.Length);
235235

236-
createdInvocation = new BoundExternInvocation(node, arrayMethod, instanceExpression,
236+
createdInvocation = new BoundExternInvocation(node, context, arrayMethod, instanceExpression,
237237
parameterExpressions);
238238
return true;
239239
}
@@ -249,7 +249,7 @@ private static bool TryCreateTMPMethodInvocation(AbstractPhaseContext context, S
249249
if (symbol.ContainingType != null &&
250250
symbol.ContainingType.ToString() == "TMPro.TMP_Text")
251251
{
252-
createdInvocation = new BoundExternInvocation(node,
252+
createdInvocation = new BoundExternInvocation(node, context,
253253
new ExternSynthesizedMethodSymbol(context, symbol.Name, instanceExpression.ValueType, symbol.Parameters.Select(e => e.Type).ToArray(), symbol.ReturnType, symbol.IsStatic),
254254
instanceExpression,
255255
parameterExpressions);
@@ -269,7 +269,7 @@ private static bool TryCreateBaseEnumMethodInvocation(AbstractPhaseContext conte
269269
symbol.ContainingType != null &&
270270
symbol.ContainingType == context.GetTypeSymbol(SpecialType.System_Enum))
271271
{
272-
createdInvocation = new BoundExternInvocation(node,
272+
createdInvocation = new BoundExternInvocation(node, context,
273273
context.GetTypeSymbol(SpecialType.System_Object).GetMember<MethodSymbol>(symbol.Name, context),
274274
instanceExpression,
275275
parameterExpressions);
@@ -289,7 +289,7 @@ private static bool TryCreateCompareToInvocation(AbstractPhaseContext context, S
289289
symbol.ContainingType != null &&
290290
symbol.ContainingType == context.GetTypeSymbol(typeof(IComparable)))
291291
{
292-
createdInvocation = new BoundExternInvocation(node,
292+
createdInvocation = new BoundExternInvocation(node, context,
293293
new ExternSynthesizedMethodSymbol(context, "CompareTo", instanceExpression.ValueType,
294294
new [] { instanceExpression.ValueType },
295295
context.GetTypeSymbol(SpecialType.System_Int32), false),
@@ -344,15 +344,6 @@ public static BoundInvocationExpression CreateBoundInvocation(AbstractPhaseConte
344344
if (CompilerUdonInterface.IsUdonEvent(symbol.Name) &&
345345
symbol.ContainingType == context.GetTypeSymbol(typeof(UdonSharpBehaviour))) // Pass through for making base calls on the U# behaviour type return noop
346346
return new BoundUdonSharpBehaviourInvocationExpression(node, symbol, instanceExpression, parameterExpressions);
347-
348-
var doExposureCheck = (!symbol.IsOperator || (symbol.ContainingType == null || !symbol.ContainingType.IsEnum));
349-
350-
if (symbol.IsOperator && symbol is ExternBuiltinOperatorSymbol operatorSymbol &&
351-
operatorSymbol.OperatorType == BuiltinOperatorType.BitwiseNot)
352-
doExposureCheck = false;
353-
354-
if (doExposureCheck && !CompilerUdonInterface.IsExposedToUdon(((ExternMethodSymbol) symbol).ExternSignature))
355-
throw new NotExposedException(LocStr.CE_UdonMethodNotExposed, node, $"{symbol.RoslynSymbol?.ToDisplayString() ?? symbol.ToString()}, sig: {((ExternMethodSymbol) symbol).ExternSignature}");
356347

357348
if (symbol.IsOperator)
358349
{
@@ -371,7 +362,7 @@ public static BoundInvocationExpression CreateBoundInvocation(AbstractPhaseConte
371362
BuiltinOperatorType.UnaryNegation, context.GetTypeSymbol(SpecialType.System_Boolean),
372363
context);
373364

374-
return new BoundExternInvocation(node, boolNotOperator, null, new BoundExpression[] {boundEqualsInvocation});
365+
return new BoundExternInvocation(node, context, boolNotOperator, null, new BoundExpression[] {boundEqualsInvocation});
375366
}
376367

377368
if (node is AssignmentExpressionSyntax)
@@ -383,13 +374,13 @@ public static BoundInvocationExpression CreateBoundInvocation(AbstractPhaseConte
383374

384375
if (parameterExpressions.Length == 2 || symbol.Name == "op_UnaryNegation" || symbol.Name == "op_LogicalNot")
385376
{
386-
return new BoundBuiltinOperatorInvocationExpression(node, symbol, parameterExpressions);
377+
return new BoundBuiltinOperatorInvocationExpression(node, context, symbol, parameterExpressions);
387378
}
388379

389380
throw new NotSupportedException("Operator expressions must have either 1 or 2 parameters", node.GetLocation());
390381
}
391382

392-
return new BoundExternInvocation(node, symbol, instanceExpression, parameterExpressions);
383+
return new BoundExternInvocation(node, context, symbol, instanceExpression, parameterExpressions);
393384
}
394385

395386
if (symbol.IsStatic)
@@ -596,8 +587,8 @@ protected void PopRecursiveValues(Value[] values, EmitContext context)
596587

597588
private sealed class BoundBuiltinOperatorInvocationExpression : BoundExternInvocation
598589
{
599-
public BoundBuiltinOperatorInvocationExpression(SyntaxNode node, MethodSymbol method, BoundExpression[] operandExpressions)
600-
:base(node, method, null, operandExpressions)
590+
public BoundBuiltinOperatorInvocationExpression(SyntaxNode node, AbstractPhaseContext context, MethodSymbol method, BoundExpression[] operandExpressions)
591+
:base(node, context, method, null, operandExpressions)
601592
{
602593
}
603594
}
@@ -642,7 +633,7 @@ private sealed class BoundGetUnityEngineComponentInvocation : BoundExternInvocat
642633
public override TypeSymbol ValueType { get; }
643634

644635
public BoundGetUnityEngineComponentInvocation(AbstractPhaseContext context, SyntaxNode node, MethodSymbol methodSymbol, BoundExpression sourceExpression, BoundExpression[] parametersExpressions)
645-
: base(node, BuildMethod(context, methodSymbol), sourceExpression, GetParameterExpressions(context, methodSymbol, parametersExpressions))
636+
: base(node, context, BuildMethod(context, methodSymbol), sourceExpression, GetParameterExpressions(context, methodSymbol, parametersExpressions))
646637
{
647638
ValueType = methodSymbol.TypeArguments[0];
648639

0 commit comments

Comments
 (0)