Skip to content

Commit d585540

Browse files
committed
Support Nullable Reference Type annotations
1 parent aee330b commit d585540

1 file changed

Lines changed: 51 additions & 4 deletions

File tree

src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ public static void Generate(LuaDocumentation docs, string path)
148148
else
149149
{
150150
sb.Append($"---@param {parameter.Name}");
151-
if (parameter.IsOptional || IsNullable(parameter.ParameterType))
151+
if (CanBeNil(parameter))
152152
{
153153
sb.Append('?');
154154
}
@@ -170,7 +170,13 @@ public static void Generate(LuaDocumentation docs, string path)
170170
if (func.Method.ReturnType != typeof(void))
171171
{
172172
sb.Append("---@return ");
173-
sb.Append(GetLuaType(func.Method.ReturnParameter));
173+
var luaType = GetLuaType(func.Method.ReturnParameter);
174+
var nilable = CanBeNil(func.Method.ReturnParameter);
175+
var wrapType = nilable && luaType.IndexOfAny([ ':', '|' ]) != -1; // ? is ambiguous on complex types like `string|int` or `fun(): string`
176+
if (wrapType) sb.Append('(');
177+
sb.Append(luaType);
178+
if (wrapType) sb.Append(')');
179+
if (nilable) sb.Append('?');
174180
if (IsZeroIndexed(func.Method.ReturnParameter))
175181
{
176182
sb.Append(" # Zero-indexed array.");
@@ -261,7 +267,7 @@ private static string GetLuaType(Type type)
261267
return GetLuaType(type.GetElementType()) + "[]";
262268
}
263269

264-
if (IsNullable(type))
270+
if (IsNullableValueType(type))
265271
{
266272
type = type.GetGenericArguments()[0];
267273
}
@@ -274,7 +280,48 @@ private static string GetLuaType(Type type)
274280
throw new NotSupportedException($"Unknown type {type.FullName} used in API. Generator must be updated to handle this.");
275281
}
276282

277-
private static bool IsNullable(Type type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
283+
private static bool CanBeNil(ParameterInfo parameter) => parameter.HasDefaultValue || IsNullableValueType(parameter.ParameterType) || IsNullableReferenceType(parameter);
284+
285+
private static bool IsNullableValueType(Type type) => type.IsValueType && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
286+
287+
/// <summary>
288+
/// Returns <see langword="true"/> if <paramref name="parameter"/> is a reference type and is annotated as nullable or lacks NRT annotations.
289+
/// </summary>
290+
/// <remarks>
291+
/// Only handles "top-level" types, not array elements or generic type parameters.
292+
/// </remarks>
293+
private static bool IsNullableReferenceType(ParameterInfo parameter)
294+
{
295+
if (parameter.ParameterType.IsValueType) return false;
296+
297+
// https://github.com/dotnet/roslyn/blob/main/docs/features/nullable-metadata.md
298+
const byte AnnotatedNotNull = 1;
299+
300+
// Check [Nullable] on the parameter first
301+
if (GetNullableFlags(parameter) is byte[] flags)
302+
{
303+
return flags[0] != AnnotatedNotNull;
304+
}
305+
306+
// Check [NullableContext] on the method and parent types
307+
var parent = parameter.Member;
308+
while (parent is not null)
309+
{
310+
if (GetNullableContext(parent) is byte flag)
311+
{
312+
return flag != AnnotatedNotNull;
313+
}
314+
parent = parent.DeclaringType;
315+
}
316+
317+
return true;
318+
319+
// Attributes may be compiled into each assembly, so can't be strongly typed
320+
static byte[]? GetNullableFlags(ParameterInfo parameter) =>
321+
((dynamic) parameter.GetCustomAttributes().SingleOrDefault(attr => attr.GetType().FullName == "System.Runtime.CompilerServices.NullableAttribute"))?.NullableFlags;
322+
static byte? GetNullableContext(MemberInfo member) =>
323+
((dynamic) member.GetCustomAttributes().SingleOrDefault(attr => attr.GetType().FullName == "System.Runtime.CompilerServices.NullableContextAttribute"))?.Flag;
324+
}
278325

279326
private static bool IsParams(ParameterInfo parameter) => parameter.GetCustomAttribute<ParamArrayAttribute>() is not null;
280327

0 commit comments

Comments
 (0)