Skip to content

Add support for searching for alternate invocees for externs #11

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
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 @@ -27,7 +27,7 @@ public ArrayConstructorExtern(AbstractPhaseContext context, TypeSymbol arrayType
private BoundExpression[] Initializers { get; }

public BoundArrayCreationExpression(SyntaxNode node, AbstractPhaseContext context, TypeSymbol arrayType, BoundExpression[] rankSizes, BoundExpression[] initializers)
: base(node, new ArrayConstructorExtern(context, arrayType), null, rankSizes)
: base(node, context, new ArrayConstructorExtern(context, arrayType), null, rankSizes)
{
ArrayType = arrayType;
Initializers = initializers;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,67 @@

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
using UdonSharp.Compiler.Udon;
using UdonSharp.Core;
using UdonSharp.Localization;

namespace UdonSharp.Compiler.Binder
{
internal class BoundExternInvocation : BoundInvocationExpression
{
public BoundExternInvocation(SyntaxNode node, MethodSymbol method, BoundExpression instanceExpression, BoundExpression[] parameterExpressions)
private ExternMethodSymbol externMethodSymbol;

public BoundExternInvocation(SyntaxNode node, AbstractPhaseContext context, MethodSymbol method, BoundExpression instanceExpression, BoundExpression[] parameterExpressions)
: base(node, method, instanceExpression, parameterExpressions)
{
externMethodSymbol = (ExternMethodSymbol)method;
if (!CompilerUdonInterface.IsExposedToUdon(externMethodSymbol.ExternSignature))
{
externMethodSymbol = FindAlternateInvocation(context, method, instanceExpression, parameterExpressions);
if (externMethodSymbol == null)
{
throw new NotExposedException(LocStr.CE_UdonMethodNotExposed, node, $"{method.RoslynSymbol?.ToDisplayString() ?? method.ToString()}, sig: {((ExternMethodSymbol)method).ExternSignature}");
}
}
}

private ExternMethodSymbol FindAlternateInvocation(AbstractPhaseContext context, MethodSymbol originalSymbol, BoundExpression instanceExpression, BoundExpression[] parameterExpressions)
{
if (originalSymbol.IsStatic || originalSymbol.IsConstructor) return null;

List<TypeSymbol> candidates = new List<TypeSymbol>();
FindCandidateInvocationTypes(context, candidates, instanceExpression.ValueType);

TypeSymbol[] paramTypes = parameterExpressions.Select(ex => ex.ValueType).ToArray();

foreach (var candidate in candidates)
{
ExternMethodSymbol externMethodSymbol = new ExternSynthesizedMethodSymbol(context, originalSymbol.Name, candidate, paramTypes, originalSymbol.ReturnType, false, false);
if (CompilerUdonInterface.IsExposedToUdon(externMethodSymbol.ExternSignature))
{
return externMethodSymbol;
}
}

return null;
}

void FindCandidateInvocationTypes(AbstractPhaseContext context, List<TypeSymbol> candidates, TypeSymbol ty)
{
foreach (var intf in ty.RoslynSymbol.AllInterfaces)
{
candidates.Add(context.GetTypeSymbol(intf));
}

while (ty != null)
{
candidates.Add(ty);
ty = ty.BaseType;
}
}

public override Value EmitValue(EmitContext context)
Expand Down Expand Up @@ -83,7 +133,7 @@ public override Value EmitValue(EmitContext context)
module.AddPush(returnValue);
}

module.AddExtern((ExternMethodSymbol) Method);
module.AddExtern(externMethodSymbol);

if (recursiveValues != null)
PopRecursiveValues(recursiveValues, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ private static bool TryCreateInstantiationInvocation(AbstractPhaseContext contex
switch (symbol.Name)
{
case "Instantiate_Extern" when symbol.ContainingType == context.GetTypeSymbol(typeof(InstantiationShim)):
createdInvocation = new BoundExternInvocation(node,
createdInvocation = new BoundExternInvocation(node, context,
new ExternSynthesizedMethodSymbol(context,
"VRCInstantiate.__Instantiate__UnityEngineGameObject__UnityEngineGameObject",
parameterExpressions.Select(e => e.ValueType).ToArray(),
Expand Down Expand Up @@ -213,7 +213,7 @@ private static bool TryCreateSetProgramVariableInvocation(AbstractPhaseContext c
.GetMembers<MethodSymbol>("SetProgramVariable", context)
.First(e => !e.RoslynSymbol.IsGenericMethod);

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

createdInvocation = new BoundExternInvocation(node, arrayMethod, instanceExpression,
createdInvocation = new BoundExternInvocation(node, context, arrayMethod, instanceExpression,
parameterExpressions);
return true;
}
Expand All @@ -249,7 +249,7 @@ private static bool TryCreateTMPMethodInvocation(AbstractPhaseContext context, S
if (symbol.ContainingType != null &&
symbol.ContainingType.ToString() == "TMPro.TMP_Text")
{
createdInvocation = new BoundExternInvocation(node,
createdInvocation = new BoundExternInvocation(node, context,
new ExternSynthesizedMethodSymbol(context, symbol.Name, instanceExpression.ValueType, symbol.Parameters.Select(e => e.Type).ToArray(), symbol.ReturnType, symbol.IsStatic),
instanceExpression,
parameterExpressions);
Expand All @@ -269,7 +269,7 @@ private static bool TryCreateBaseEnumMethodInvocation(AbstractPhaseContext conte
symbol.ContainingType != null &&
symbol.ContainingType == context.GetTypeSymbol(SpecialType.System_Enum))
{
createdInvocation = new BoundExternInvocation(node,
createdInvocation = new BoundExternInvocation(node, context,
context.GetTypeSymbol(SpecialType.System_Object).GetMember<MethodSymbol>(symbol.Name, context),
instanceExpression,
parameterExpressions);
Expand All @@ -289,7 +289,7 @@ private static bool TryCreateCompareToInvocation(AbstractPhaseContext context, S
symbol.ContainingType != null &&
symbol.ContainingType == context.GetTypeSymbol(typeof(IComparable)))
{
createdInvocation = new BoundExternInvocation(node,
createdInvocation = new BoundExternInvocation(node, context,
new ExternSynthesizedMethodSymbol(context, "CompareTo", instanceExpression.ValueType,
new [] { instanceExpression.ValueType },
context.GetTypeSymbol(SpecialType.System_Int32), false),
Expand Down Expand Up @@ -344,15 +344,6 @@ public static BoundInvocationExpression CreateBoundInvocation(AbstractPhaseConte
if (CompilerUdonInterface.IsUdonEvent(symbol.Name) &&
symbol.ContainingType == context.GetTypeSymbol(typeof(UdonSharpBehaviour))) // Pass through for making base calls on the U# behaviour type return noop
return new BoundUdonSharpBehaviourInvocationExpression(node, symbol, instanceExpression, parameterExpressions);

bool doExposureCheck = (!symbol.IsOperator || (symbol.ContainingType == null || !symbol.ContainingType.IsEnum));

if (symbol.IsOperator && symbol is ExternBuiltinOperatorSymbol operatorSymbol &&
operatorSymbol.OperatorType == BuiltinOperatorType.BitwiseNot)
doExposureCheck = false;

if (doExposureCheck && !CompilerUdonInterface.IsExposedToUdon(((ExternMethodSymbol) symbol).ExternSignature))
throw new NotExposedException(LocStr.CE_UdonMethodNotExposed, node, $"{symbol.RoslynSymbol?.ToDisplayString() ?? symbol.ToString()}, sig: {((ExternMethodSymbol) symbol).ExternSignature}");

if (symbol.IsOperator)
{
Expand All @@ -362,7 +353,7 @@ public static BoundInvocationExpression CreateBoundInvocation(AbstractPhaseConte
MethodSymbol objectEqualsMethod = context.GetTypeSymbol(SpecialType.System_Object)
.GetMember<MethodSymbol>("Equals", context);

BoundInvocationExpression boundEqualsInvocation = CreateBoundInvocation(context, node, objectEqualsMethod, parameterExpressions[0],
var boundEqualsInvocation = CreateBoundInvocation(context, node, objectEqualsMethod, parameterExpressions[0],
new[] {parameterExpressions[1]});
if (symbol.Name == "op_Equality")
return boundEqualsInvocation;
Expand All @@ -371,7 +362,7 @@ public static BoundInvocationExpression CreateBoundInvocation(AbstractPhaseConte
BuiltinOperatorType.UnaryNegation, context.GetTypeSymbol(SpecialType.System_Boolean),
context);

return new BoundExternInvocation(node, boolNotOperator, null, new BoundExpression[] {boundEqualsInvocation});
return new BoundExternInvocation(node, context, boolNotOperator, null, new BoundExpression[] {boundEqualsInvocation});
}

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

if (parameterExpressions.Length == 2 || symbol.Name == "op_UnaryNegation" || symbol.Name == "op_LogicalNot")
{
return new BoundBuiltinOperatorInvocationExpression(node, symbol, parameterExpressions);
return new BoundBuiltinOperatorInvocationExpression(node, context, symbol, parameterExpressions);
}

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

return new BoundExternInvocation(node, symbol, instanceExpression, parameterExpressions);
return new BoundExternInvocation(node, context, symbol, instanceExpression, parameterExpressions);
}

if (symbol.IsStatic)
Expand All @@ -404,7 +395,7 @@ public static BoundInvocationExpression CreateBoundInvocation(AbstractPhaseConte
parameterExpressions);
}

throw new NotImplementedException();
throw new System.NotImplementedException();
}

protected override void ReleaseCowValuesImpl(EmitContext context)
Expand Down Expand Up @@ -596,8 +587,8 @@ protected void PopRecursiveValues(Value[] values, EmitContext context)

private sealed class BoundBuiltinOperatorInvocationExpression : BoundExternInvocation
{
public BoundBuiltinOperatorInvocationExpression(SyntaxNode node, MethodSymbol method, BoundExpression[] operandExpressions)
:base(node, method, null, operandExpressions)
public BoundBuiltinOperatorInvocationExpression(SyntaxNode node, AbstractPhaseContext context, MethodSymbol method, BoundExpression[] operandExpressions)
:base(node, context, method, null, operandExpressions)
{
}
}
Expand Down Expand Up @@ -642,7 +633,7 @@ private sealed class BoundGetUnityEngineComponentInvocation : BoundExternInvocat
public override TypeSymbol ValueType { get; }

public BoundGetUnityEngineComponentInvocation(AbstractPhaseContext context, SyntaxNode node, MethodSymbol methodSymbol, BoundExpression sourceExpression, BoundExpression[] parametersExpressions)
: base(node, BuildMethod(context, methodSymbol), sourceExpression, GetParameterExpressions(context, methodSymbol, parametersExpressions))
: base(node, context, BuildMethod(context, methodSymbol), sourceExpression, GetParameterExpressions(context, methodSymbol, parametersExpressions))
{
ValueType = methodSymbol.TypeArguments[0];

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c333ccfdd0cbdbc4ca30cef2dd6e6b9b, type: 3}
m_Name: AccessViaAlternateInvocee
m_EditorClassIdentifier:
serializedUdonProgramAsset: {fileID: 11400000, guid: 8876b3583cec99942b84819c559e175c,
type: 2}
udonAssembly:
assemblyError:
sourceCsScript: {fileID: 11500000, guid: f4acb09f599aab7418e27fcf7aff58e3, type: 3}
behaviourSyncMode: 0
compileErrors: []
hasInteractEvent: 0
serializationData:
SerializedFormat: 2
SerializedBytes:
ReferencedUnityObjects: []
SerializedBytesString:
Prefab: {fileID: 0}
PrefabModificationsReferencedUnityObjects: []
PrefabModifications: []
SerializationNodes:
- Name: fieldDefinitions
Entry: 7
Data: 0|System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[UdonSharp.Compiler.FieldDefinition,
UdonSharp.Editor]], mscorlib
- Name: comparer
Entry: 7
Data: 1|System.Collections.Generic.GenericEqualityComparer`1[[System.String,
mscorlib]], mscorlib
- Name:
Entry: 8
Data:
- Name:
Entry: 12
Data: 1
- Name:
Entry: 7
Data:
- Name: $k
Entry: 1
Data: tester
- Name: $v
Entry: 7
Data: 2|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
- Name: <Name>k__BackingField
Entry: 1
Data: tester
- Name: <UserType>k__BackingField
Entry: 7
Data: 3|System.RuntimeType, mscorlib
- Name:
Entry: 1
Data: UdonSharp.Tests.IntegrationTestSuite, Assembly-CSharp
- Name:
Entry: 8
Data:
- Name: <SystemType>k__BackingField
Entry: 7
Data: 4|System.RuntimeType, mscorlib
- Name:
Entry: 1
Data: VRC.Udon.UdonBehaviour, VRC.Udon
- Name:
Entry: 8
Data:
- Name: <SyncMode>k__BackingField
Entry: 7
Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
- Name:
Entry: 6
Data:
- Name:
Entry: 8
Data:
- Name: <IsSerialized>k__BackingField
Entry: 5
Data: false
- Name: fieldAttributes
Entry: 7
Data: 5|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
- Name:
Entry: 12
Data: 1
- Name:
Entry: 7
Data: 6|System.NonSerializedAttribute, mscorlib
- Name:
Entry: 8
Data:
- Name:
Entry: 13
Data:
- Name:
Entry: 8
Data:
- Name: userBehaviourSource
Entry: 6
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 13
Data:
- Name:
Entry: 8
Data:

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;

namespace UdonSharp.Tests
{
/// <summary>
/// Certain Udon externs are exposed not via the C# type or interface they're implemented on, but rather
/// on some subclass directly. This test verifies that we can search for such an alternate subclass and
/// use it for the extern invocation.
/// </summary>
[AddComponentMenu("Udon Sharp/Tests/AccessViaAlternateInvocee")]
public class AccessViaAlternateInvocee : UdonSharpBehaviour
{
[System.NonSerialized]
public IntegrationTestSuite tester;

public void ExecuteTests()
{
System.Type tyString = typeof(string);
tester.TestAssertion("Access System.Type.Name", tyString.Name.Equals("String"));
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.