Unverified Commit 9026d802 authored by dcronqvist's avatar dcronqvist Committed by GitHub
Browse files

Add LuaMemberAttribute (#489)

* Registering functions now only register 1 global, fix #480

* Changed `is not null` to `!= null` for backwards compatibility

* Use `IsSubclassOf` instead of newer `IsAssignableTo`

* Explicit type for anonymous function in unit test, for pre-C#10 compatibility

* Only include test on `NETCOREAPP3_1_OR_GREATER`, added comment for test as well

* Removed `NETCOREAPP3_1_OR_GREATER` check because it is unnecessary

* Add LuaMemberAttribute, allows registered name in Lua to be different from C#, closes #488

* Explicit typing fix

* Tests use IEnumerable<T>.Contains to check for existence instead
parent 656191ee
using System;
namespace NLua
{
/// <summary>
/// Marks a method for global usage in Lua scripts
/// </summary>
/// <see cref="LuaRegistrationHelper.TaggedInstanceMethods"/>
/// <see cref="LuaRegistrationHelper.TaggedStaticMethods"/>
[AttributeUsage(AttributeTargets.Method)]
public sealed class LuaGlobalAttribute : Attribute
{
/// <summary>
/// An alternative name to use for calling the function in Lua - leave empty for CLR name
/// </summary>
public string Name { get; set; }
/// <summary>
/// A description of the function
/// </summary>
public string Description { get; set; }
}
}
\ No newline at end of file
......@@ -150,7 +150,6 @@ namespace NLua
if (
// Check that the LuaHideAttribute and LuaGlobalAttribute were not applied
(!method.GetCustomAttributes(typeof(LuaHideAttribute), false).Any()) &&
(!method.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Any()) &&
// Exclude some generic .NET methods that wouldn't be very usefull in Lua
name != "GetType" && name != "GetHashCode" && name != "Equals" &&
name != "ToString" && name != "Clone" && name != "Dispose" &&
......@@ -160,6 +159,13 @@ namespace NLua
!name.StartsWith("add_", StringComparison.Ordinal) &&
!name.StartsWith("remove_", StringComparison.Ordinal))
{
if (method.GetCustomAttributes(typeof(LuaMemberAttribute), false).Any())
{
// If the LuaGlobalAttribute was applied, use the name specified in the attribute
var attribute = (LuaMemberAttribute)method.GetCustomAttributes(typeof(LuaMemberAttribute), false).First();
name = attribute.Name;
}
// Format for easy method invocation
string command = path + ":" + name + "(";
......@@ -177,11 +183,19 @@ namespace NLua
{
if (
// Check that the LuaHideAttribute and LuaGlobalAttribute were not applied
(!field.GetCustomAttributes(typeof(LuaHideAttribute), false).Any()) &&
(!field.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Any()))
(!field.GetCustomAttributes(typeof(LuaHideAttribute), false).Any()))
{
string name = field.Name;
if (field.GetCustomAttributes(typeof(LuaMemberAttribute), false).Any())
{
// If the LuaGlobalAttribute was applied, use the name specified in the attribute
var attribute = (LuaMemberAttribute)field.GetCustomAttributes(typeof(LuaMemberAttribute), false).First();
name = attribute.Name;
}
// Go into recursion for members
RegisterPath(path + "." + field.Name, field.FieldType, recursionCounter + 1, entry);
RegisterPath(path + "." + name, field.FieldType, recursionCounter + 1, entry);
}
}
#endregion
......@@ -191,13 +205,21 @@ namespace NLua
{
if (
// Check that the LuaHideAttribute and LuaGlobalAttribute were not applied
(!property.GetCustomAttributes(typeof(LuaHideAttribute), false).Any()) &&
(!property.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Any())
(!property.GetCustomAttributes(typeof(LuaHideAttribute), false).Any())
// Exclude some generic .NET properties that wouldn't be very useful in Lua
&& property.Name != "Item")
{
string name = property.Name;
if (property.GetCustomAttributes(typeof(LuaMemberAttribute), false).Any())
{
// If the LuaGlobalAttribute was applied, use the name specified in the attribute
var attribute = (LuaMemberAttribute)property.GetCustomAttributes(typeof(LuaMemberAttribute), false).First();
name = attribute.Name;
}
// Go into recursion for members
RegisterPath(path + "." + property.Name, property.PropertyType, recursionCounter + 1, entry);
RegisterPath(path + "." + name, property.PropertyType, recursionCounter + 1, entry);
}
}
#endregion
......
......@@ -3,7 +3,7 @@ using System;
namespace NLua
{
/// <summary>
/// Marks a method, field or property to be hidden from Lua auto-completion
/// Marks a method, field or property to be hidden from Lua
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public sealed class LuaHideAttribute : Attribute
......
using System;
using System.Linq;
using System.Reflection;
namespace NLua
{
/// <summary>
/// Allows the user to specify the name of the member when accessed in Lua
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field)]
public sealed class LuaMemberAttribute : Attribute
{
/// <summary>
/// The name of the member when used accessed in Lua
/// </summary>
public string Name { get; set; }
public static MethodInfo[] GetMethodsForType(Type type, string methodName, BindingFlags bindingFlags, Type[] signature)
{
return type.GetMethods(bindingFlags).Where(m =>
{
if (m.GetCustomAttribute<LuaHideAttribute>() != null)
return false;
if (m.GetCustomAttribute<LuaMemberAttribute>() != null)
{
var attr = m.GetCustomAttribute<LuaMemberAttribute>();
return attr.Name == methodName && m.GetParameters().Select(p => p.ParameterType).SequenceEqual(signature);
}
return m.Name == methodName && m.GetParameters().Select(p => p.ParameterType).SequenceEqual(signature);
}).ToArray();
}
public static MethodInfo[] GetMethodsForType(Type type, string methodName, BindingFlags bindingFlags)
{
return type.GetMethods(bindingFlags).Where(m =>
{
if (m.GetCustomAttribute<LuaHideAttribute>() != null)
return false;
if (m.GetCustomAttribute<LuaMemberAttribute>() != null)
{
var attr = m.GetCustomAttribute<LuaMemberAttribute>();
return attr.Name == methodName;
}
return m.Name == methodName;
}).ToArray();
}
public static MemberInfo[] GetMembersForType(Type type, string memberName, BindingFlags bindingFlags)
{
return type.GetMembers(bindingFlags).Where(m =>
{
if (m.GetCustomAttribute<LuaHideAttribute>() != null)
return false;
if (m.GetCustomAttribute<LuaMemberAttribute>() != null)
{
var attr = m.GetCustomAttribute<LuaMemberAttribute>();
return attr.Name == memberName;
}
return m.Name == memberName;
}).ToArray();
}
}
}
\ No newline at end of file
......@@ -8,7 +8,7 @@ namespace NLua
{
#region Tagged instance methods
/// <summary>
/// Registers all public instance methods in an object tagged with <see cref="LuaGlobalAttribute"/> as Lua global functions
/// Registers all public instance methods in an object tagged with <see cref="LuaMemberAttribute"/> as Lua global functions
/// </summary>
/// <param name="lua">The Lua VM to add the methods to</param>
/// <param name="o">The object to get the methods from</param>
......@@ -24,7 +24,7 @@ namespace NLua
foreach (var method in o.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public))
{
foreach (LuaGlobalAttribute attribute in method.GetCustomAttributes(typeof(LuaGlobalAttribute), true))
foreach (LuaMemberAttribute attribute in method.GetCustomAttributes(typeof(LuaMemberAttribute), true))
{
if (string.IsNullOrEmpty(attribute.Name))
lua.RegisterFunction(method.Name, o, method); // CLR name
......@@ -37,7 +37,7 @@ namespace NLua
#region Tagged static methods
/// <summary>
/// Registers all public static methods in a class tagged with <see cref="LuaGlobalAttribute"/> as Lua global functions
/// Registers all public static methods in a class tagged with <see cref="LuaMemberAttribute"/> as Lua global functions
/// </summary>
/// <param name="lua">The Lua VM to add the methods to</param>
/// <param name="type">The class type to get the methods from</param>
......@@ -56,7 +56,7 @@ namespace NLua
foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public))
{
foreach (LuaGlobalAttribute attribute in method.GetCustomAttributes(typeof(LuaGlobalAttribute), false))
foreach (LuaMemberAttribute attribute in method.GetCustomAttributes(typeof(LuaMemberAttribute), false))
{
if (string.IsNullOrEmpty(attribute.Name))
lua.RegisterFunction(method.Name, null, method); // CLR name
......
......@@ -91,7 +91,7 @@ namespace NLua.Method
if (type == typeof(object))
return type.GetMethods(methodName, bindingType);
var methods = type.GetMethods(methodName, bindingType);
var methods = LuaMemberAttribute.GetMethodsForType(type, methodName, bindingType);
var baseMethods = GetMethodsRecursively(type.BaseType, methodName, bindingType);
return methods.Concat(baseMethods).ToArray();
......
......@@ -25,7 +25,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Lua.cs" />
<Compile Include="$(MSBuildThisFileDirectory)LuaBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)LuaFunction.cs" />
<Compile Include="$(MSBuildThisFileDirectory)LuaGlobalAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)LuaMemberAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)LuaGlobals.cs" />
<Compile Include="$(MSBuildThisFileDirectory)LuaHideAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)LuaRegistrationHelper.cs" />
......
using System;
using System.Linq;
using System.Reflection;
namespace NLua
......@@ -42,12 +43,12 @@ namespace NLua
public MemberInfo[] GetMember(string name, BindingFlags bindingAttr)
{
return _proxy.GetMember(name, bindingAttr);
return LuaMemberAttribute.GetMembersForType(_proxy, name, bindingAttr);
}
public MethodInfo GetMethod(string name, BindingFlags bindingAttr, Type[] signature)
{
return _proxy.GetMethod(name, bindingAttr, null, signature, null);
return LuaMemberAttribute.GetMethodsForType(_proxy, name, bindingAttr, signature).FirstOrDefault();
}
}
}
\ No newline at end of file
......@@ -3243,6 +3243,44 @@ namespace NLuaTest
}
}
[Test]
public void TestNLuaAttributes()
{
using (Lua lua = new Lua())
{
var testClass = new TestClassWithNLuaAttributes();
lua["test"] = testClass;
string[] globals = lua.Globals.ToArray();
Assert.True(globals.Contains("test.PropWithoutAttribute"));
Assert.True(globals.Contains("test.prop_with_attribute"));
Assert.True(globals.Contains("test.fieldWithoutAttribute"));
Assert.True(globals.Contains("test.field_with_attribute"));
// Methods use : instead of .
Assert.True(globals.Contains("test:MethodWithoutAttribute()"));
Assert.True(globals.Contains("test:method_with_attribute()"));
Assert.AreEqual(6, globals.Length);
Assert.AreEqual(0, lua.DoString("return test.PropWithoutAttribute")[0]);
Assert.AreEqual(1, lua.DoString("return test.prop_with_attribute")[0]);
Assert.AreEqual(2, lua.DoString("return test.fieldWithoutAttribute")[0]);
Assert.AreEqual(3, lua.DoString("return test.field_with_attribute")[0]);
Assert.AreEqual(4, lua.DoString("return test:MethodWithoutAttribute()")[0]);
Assert.AreEqual(5, lua.DoString("return test:method_with_attribute()")[0]);
// Test that accessing hidden properties/fields is the same as accessing nonexisting ones
object valueOfNonExisting = lua.DoString("return test.NonExistingProperty")[0];
Assert.AreEqual(valueOfNonExisting, lua.DoString("return test.HiddenProperty")[0]);
Assert.AreEqual(valueOfNonExisting, lua.DoString("return test.hiddenField")[0]);
// Calling nonexisting/hidden methods should throw an exception
Assert.Throws<LuaScriptException>(() => lua.DoString("return test:NonExistingMethod()"), "Non existing method should throw an exception");
Assert.Throws<LuaScriptException>(() => lua.DoString("return test:HiddenMethod()"), "Hidden method should throw an exception");
}
}
static Lua m_lua;
}
}
......@@ -26,6 +26,7 @@
<Compile Include="$(MSBuildThisFileDirectory)TestTypes\TestClass3.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestTypes\TestClassGeneric.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestTypes\TestClassWithGenericMethod.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestTypes\TestClassWithNLuaAttributes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestTypes\TestClassWithMethodDefaultParameter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestTypes\TestClassWithOverloadedMethod.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestTypes\TestEnum.cs" />
......
using System;
using NLua;
namespace NLuaTest.TestTypes
{
public class TestClassWithNLuaAttributes
{
public int PropWithoutAttribute { get; set; } = 0;
[LuaMember(Name = "prop_with_attribute")]
public int PropWithAttribute { get; set; } = 1;
public int fieldWithoutAttribute = 2;
[LuaMember(Name = "field_with_attribute")]
public int fieldWithAttribute = 3;
public int MethodWithoutAttribute()
{
return 4;
}
[LuaMember(Name = "method_with_attribute")]
public int MethodWithAttribute()
{
return 5;
}
[LuaHide]
public int HiddenProperty { get; set; } = 6;
[LuaHide]
public int hiddenField = 7;
[LuaHide]
public int HiddenMethod()
{
return 8;
}
}
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment