private bool TryExtractMethodFromAttributeData( IMethodSymbol method, INamedTypeSymbol attributeType, bool synchroneous, [NotNullWhen(returnValue: true)] out ReactMethod?reactMethod) { if (TryFindAttribute(method, attributeType, out var attr)) { string?name = null; if (attr.ConstructorArguments.Length > 0) { name = attr.ConstructorArguments[0].Value as string; } foreach (var namedArgument in attr.NamedArguments) { switch (namedArgument.Key) { case nameof(ReactMethodAttribute.MethodName): name = namedArgument.Value.Value as string; break; default: var location = attr.ApplicationSyntaxReference?.SyntaxTree.GetLocation(attr.ApplicationSyntaxReference.Span); m_diagnostics.Add(Diagnostic.Create( DiagnosticDescriptors.UnexpectedPropertyInAttribute, location ?? Location.None, namedArgument.Key, attr.AttributeClass?.Name)); reactMethod = null; return(false); } } name ??= method.Name; reactMethod = new ReactMethod(method, name, synchroneous); return(true); } reactMethod = null; return(false); }
internal StatementSyntax AddMethod(ReactMethod method) { var statements = new List <StatementSyntax>(); var parameters = method.Method.Parameters; var readArgsArguments = new List <ArgumentSyntax>(parameters.Length); readArgsArguments.Add(Argument(IdentifierName(ReactNativeNames.ReaderLocalName))); var args = new List <ExpressionSyntax>(parameters.Length); // generates: // (out ArgType0 arg0, out ArgType1 arg1, ...); // as well as // (arg0, arg1, ... ) for (int i = 0; i < method.EffectiveParameters.Count; i++) { var param = method.EffectiveParameters[i]; var variableName = "arg" + i.ToString(CultureInfo.InvariantCulture); readArgsArguments.Add(Argument( nameColon: null, refKindKeyword: Token(SyntaxKind.OutKeyword), expression: DeclarationExpression( param.Type.ToTypeSyntax(), SingleVariableDesignation( Identifier(variableName)) ) )); args.Add(IdentifierName(variableName)); } switch (method.ReturnStyle) { case ReactMethod.MethodReturnStyle.Promise: args.Add(ObjectCreationExpression( ReactTypes.ReactPromise.Construct(method.EffectiveReturnType), IdentifierName(ReactNativeNames.WriterLocalName), IdentifierName(ReactNativeNames.ResolveLocalName), IdentifierName(ReactNativeNames.RejectLocalName) )); break; case ReactMethod.MethodReturnStyle.Callback: args.Add(GeneratePromiseInvocation(isReject: false)); break; case ReactMethod.MethodReturnStyle.TwoCallbacks: args.Add(GeneratePromiseInvocation(isReject: false)); args.Add(GeneratePromiseInvocation(isReject: true)); break; } // generates: // reader.ReadArgs( ... ) statements.Add(InvocationStatement( MemberAccessExpression(ReactTypes.JSValueReader, ReactNativeNames.ReadArgsMethodName), readArgsArguments)); var methodCall = InvocationStatement( MemberAccessExpression(ReactNativeNames.Module, Identifier(method.Method.Name)), args.ToArray() ); if (method.Method.ReturnsVoid) { // generate: // module.MyMethod(arg0, arg1, ...) statements.Add(methodCall); } else { // generate: // MyResult result = module.MyMethod(arg0, arg1, ...); statements.Add( LocalDeclarationStatement( method.Method.ReturnType, ReactNativeNames.ResultLocalName, methodCall.Expression)); if (method.ReturnStyle == ReactMethod.MethodReturnStyle.Task) { // generate: // ReactTaskExtension.ContinueWith(result, writer, resolve, reject); statements.Add(InvocationStatement( MemberAccessExpression(ReactTypes.ReactTaskExtensions, ReactNativeNames.ContinueWith), IdentifierName(ReactNativeNames.ResultLocalName), IdentifierName(ReactNativeNames.WriterLocalName), IdentifierName(ReactNativeNames.ResolveLocalName), IdentifierName(ReactNativeNames.RejectLocalName) )); } else { if (method.IsSynchronous) { // generate: // writer.WriteValue(result); var writeValue = InvocationExpression( MemberAccessExpression(ReactTypes.JSValueWriter, ReactNativeNames.WriteValueMethodName), IdentifierName(ReactNativeNames.WriterLocalName), IdentifierName(ReactNativeNames.ResultLocalName)); statements.Add(ExpressionStatement(writeValue)); } else { // generate: // writer.WriteArgs(result); var writeArgs = InvocationExpression( MemberAccessExpression(ReactTypes.JSValueWriter, ReactNativeNames.WriteArgsMethodName), IdentifierName(ReactNativeNames.WriterLocalName), IdentifierName(ReactNativeNames.ResultLocalName)); // generate: // resolve(.. writeargs ..); statements.Add( InvocationStatement( IdentifierName(ReactNativeNames.ResolveLocalName), writeArgs )); } } } if (method.IsSynchronous) { // generate: // moduleBuilder.AddSyncMethod( // "MyMethod", // ( // IJSValueReader reader, // IJSValueWriter writer) => // { // .... statements // } return(InvocationStatement( MemberAccessExpression(ReactNativeNames.ModuleBuilder, ReactNativeNames.AddSyncMethod), LiteralExpression(method.Name), ParenthesizedLambdaExpression( parameterList: ParameterList( Parameter(ReactNativeNames.ReaderLocalName) .WithType(ReactTypes.IJSValueReader.ToTypeSyntax()), Parameter(ReactNativeNames.WriterLocalName) .WithType(ReactTypes.IJSValueWriter.ToTypeSyntax())), block: Block(statements), expressionBody: null))); } else { // generate: // moduleBuilder.AddMethod( // "MyMethod", // MethodReturnType.xxx, // ( // IJSValueReader reader, // IJSValueWriter writer, // MethodResultCallback resolve, // MethodResultCallback reject) => // { // .... statements // } return(InvocationStatement( MemberAccessExpression( ReactNativeNames.ModuleBuilder, method.IsSynchronous ? ReactNativeNames.AddSyncMethod : ReactNativeNames.AddMethod), LiteralExpression(method.Name), MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, ReactTypes.MethodReturnType.ToTypeSyntax(), SyntaxFactory.IdentifierName(GetMethodReturnTypeFromStyle(method.ReturnStyle))), ParenthesizedLambdaExpression( parameterList: ParameterList( Parameter(ReactNativeNames.ReaderLocalName) .WithType(ReactTypes.IJSValueReader.ToTypeSyntax()), Parameter(ReactNativeNames.WriterLocalName) .WithType(ReactTypes.IJSValueWriter.ToTypeSyntax()), Parameter(ReactNativeNames.ResolveLocalName) .WithType(ReactTypes.MethodResultCallback.ToTypeSyntax()), Parameter(ReactNativeNames.RejectLocalName) .WithType(ReactTypes.MethodResultCallback.ToTypeSyntax())), block: Block( statements), expressionBody: null))); } }
private bool TryExtractMethodFromAttributeData( IMethodSymbol method, INamedTypeSymbol attributeType, bool synchronous, [NotNullWhen(returnValue: true)] out ReactMethod?reactMethod) { Contract.Requires(m_reactTypes != null); if (TryFindAttribute(method, attributeType, out var attr)) { string?name = null; if (attr.ConstructorArguments.Length > 0) { name = attr.ConstructorArguments[0].Value as string; } foreach (var namedArgument in attr.NamedArguments) { switch (namedArgument.Key) { case nameof(ReactMethodAttribute.MethodName): name = namedArgument.Value.Value as string; break; default: var location = attr.ApplicationSyntaxReference?.SyntaxTree.GetLocation(attr.ApplicationSyntaxReference.Span); m_diagnostics.Add(Diagnostic.Create( DiagnosticDescriptors.UnexpectedPropertyInAttribute, location ?? Location.None, namedArgument.Key, attr.AttributeClass?.Name)); reactMethod = null; return(false); } } ReactMethod.MethodReturnStyle?returnStyle = null; // Compute the effective parameters var effectiveParameters = new List <IParameterSymbol>(); ITypeSymbol effectiveReturnType = m_reactTypes.SystemVoid; var parameters = method.Parameters; for (int i = 0; i < parameters.Length; i++) { var param = parameters[i]; bool skipReadingArg = false; if (!synchronous) // IsSynchronous methods don't have support for the callbacks { if (i == parameters.Length - 1) { if (IsPromiseType(parameters[i].Type, out var promiseGenericType)) { returnStyle = ReactMethod.MethodReturnStyle.Promise; effectiveReturnType = promiseGenericType; skipReadingArg = true; } // if the last argument is a callback, assume this is the resolve or resolve function if (IsSingleArgCallback(parameters[i].Type, out var currentCallBackType)) { ITypeSymbol?previousCallBackType = null; var isReject = parameters.Length >= 2 && IsSingleArgCallback(parameters[i - 1].Type, out previousCallBackType); effectiveReturnType = isReject ? previousCallBackType ! : currentCallBackType !; returnStyle = isReject ? ReactMethod.MethodReturnStyle.TwoCallbacks : ReactMethod.MethodReturnStyle.Callback; skipReadingArg = true; } } else if (i == parameters.Length - 2) { // if the last 2 argument are callbacks, assume this is the resolve function if (IsSingleArgCallback(parameters[i].Type, out var resolveCallBackType) && IsSingleArgCallback(parameters[i + 1].Type, out _)) { effectiveReturnType = resolveCallBackType; returnStyle = ReactMethod.MethodReturnStyle.TwoCallbacks; skipReadingArg = true; } } } if (!skipReadingArg) { effectiveParameters.Add(param); } } if (returnStyle == null) { if (IsTaskType(method.ReturnType, out var taskReturnType)) { if (synchronous) { m_diagnostics.Add(Diagnostic.Create( DiagnosticDescriptors.MethodShouldNotReturnTaskWhenSynchronous, method.GetLocation() ?? Location.None, method.Name, attr.AttributeClass?.Name)); reactMethod = null; return(false); } effectiveReturnType = taskReturnType; returnStyle = ReactMethod.MethodReturnStyle.Task; } else if (method.ReturnsVoid) { returnStyle = ReactMethod.MethodReturnStyle.Void; } else { returnStyle = ReactMethod.MethodReturnStyle.ReturnValue; } } name ??= method.Name; reactMethod = new ReactMethod(method, name, returnStyle.Value, effectiveReturnType, effectiveParameters, synchronous); return(true); } reactMethod = null; return(false); }
internal StatementSyntax AddMethod(ReactMethod method) { var statements = new List <StatementSyntax>(); string methodReturnType = "Void"; var parameters = method.Method.Parameters; var readArgsArguments = new List <ArgumentSyntax>(parameters.Length); readArgsArguments.Add(Argument(IdentifierName(ReactNativeNames.ReaderLocalName))); var args = new List <ExpressionSyntax>(parameters.Length); // generates: // (out ArgType0 arg0, out ArgType1 arg1, ...); // as well as // (arg0, arg1, ... ) for (int i = 0; i < parameters.Length; i++) { var param = parameters[i]; bool skipReadingArg = false; if (!method.IsSynchronous) // IsSynchronous methods don't have support for the callbacks { if (i == parameters.Length - 1) { if (IsPromiseType(parameters[i].Type, out var typeParameter)) { // outArgs: Do not add an extra out parameter to be extracted from the JS side // generates: // new ReactPromise<TResult>(writer, resolve, reject) // to (arg0, arg1, ...) to be passed when calling the remoted method. args.Add(ObjectCreationExpression( m_reactTypes.ReactPromise.Construct(typeParameter), IdentifierName(ReactNativeNames.WriterLocalName), IdentifierName(ReactNativeNames.ResolveLocalName), IdentifierName(ReactNativeNames.RejectLocalName) )); methodReturnType = "Promise"; skipReadingArg = true; } // if the last argument is a callback, assume this is the resolve or resolve function if (IsSingleArgCallback(parameters[i].Type, out _)) { var isReject = parameters.Length >= 2 && IsSingleArgCallback(parameters[i - 1].Type, out _); args.Add(GeneratePromiseInvocation(isReject)); methodReturnType = isReject ? "TwoCallbacks" : "Callback"; skipReadingArg = true; } } else if (i == parameters.Length - 2) { // if the last 2 argument is a callback, assume this is the resolve function if (IsSingleArgCallback(parameters[i].Type, out _) && IsSingleArgCallback(parameters[i + 1].Type, out _)) { args.Add(GeneratePromiseInvocation(isReject: false)); skipReadingArg = true; } } } if (!skipReadingArg) { var variableName = "arg" + i.ToString(CultureInfo.InvariantCulture); readArgsArguments.Add(Argument( nameColon: null, refKindKeyword: Token(SyntaxKind.OutKeyword), expression: DeclarationExpression( param.Type.ToTypeSyntax(), SingleVariableDesignation( Identifier(variableName)) ) )); args.Add(IdentifierName(variableName)); } } // generates: // reader.ReadArgs( ... ) statements.Add(InvocationStatement( MemberAccessExpression(m_reactTypes.JSValueReader, ReactNativeNames.ReadArgsMethodName), readArgsArguments)); var methodCall = InvocationStatement( MemberAccessExpression(ReactNativeNames.Module, Identifier(method.Method.Name)), args.ToArray() ); if (method.Method.ReturnsVoid) { // generate: // module.MyMethod(arg0, arg1, ...) statements.Add(methodCall); } else { methodReturnType = "Callback"; // generate: // MyResult result = module.MyMethod(arg0, arg1, ...); statements.Add( LocalDeclarationStatement( method.Method.ReturnType, ReactNativeNames.ResultLocalName, methodCall.Expression)); // generate: // writer.WriteArgs(result); var writeArgs = InvocationExpression( MemberAccessExpression(m_reactTypes.JSValueWriter, ReactNativeNames.WriteArgsMethodName), IdentifierName(ReactNativeNames.WriterLocalName), IdentifierName(ReactNativeNames.ResultLocalName)); if (method.IsSynchronous) { statements.Add(ExpressionStatement(writeArgs)); } else { // generate: // resolve(.. writeargs ..); statements.Add( InvocationStatement( IdentifierName(ReactNativeNames.ResolveLocalName), writeArgs )); } } if (method.IsSynchronous) { // generate: // moduleBuilder.AddSyncMethod( // "MyMethod", // ( // IJSValueReader reader, // IJSValueWriter writer) => // { // .... statements // } return(InvocationStatement( MemberAccessExpression(ReactNativeNames.ModuleBuilder, ReactNativeNames.AddSyncMethod), LiteralExpression(method.Name), ParenthesizedLambdaExpression( parameterList: ParameterList( Parameter(ReactNativeNames.ReaderLocalName) .WithType(m_reactTypes.IJSValueReader.ToTypeSyntax()), Parameter(ReactNativeNames.WriterLocalName) .WithType(m_reactTypes.IJSValueWriter.ToTypeSyntax())), block: Block(statements), expressionBody: null))); } else { // generate: // moduleBuilder.AddMethod( // "MyMethod", // MethodReturnType.xxx, // ( // IJSValueReader reader, // IJSValueWriter writer, // MethodResultCallback resolve, // MethodResultCallback reject) => // { // .... statements // } return(InvocationStatement( MemberAccessExpression( ReactNativeNames.ModuleBuilder, method.IsSynchronous ? ReactNativeNames.AddSyncMethod : ReactNativeNames.AddMethod), LiteralExpression(method.Name), MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, m_reactTypes.MethodReturnType.ToTypeSyntax(), SyntaxFactory.IdentifierName(methodReturnType)), ParenthesizedLambdaExpression( parameterList: ParameterList( Parameter(ReactNativeNames.ReaderLocalName) .WithType(m_reactTypes.IJSValueReader.ToTypeSyntax()), Parameter(ReactNativeNames.WriterLocalName) .WithType(m_reactTypes.IJSValueWriter.ToTypeSyntax()), Parameter(ReactNativeNames.ResolveLocalName) .WithType(m_reactTypes.MethodResultCallback.ToTypeSyntax()), Parameter(ReactNativeNames.RejectLocalName) .WithType(m_reactTypes.MethodResultCallback.ToTypeSyntax())), block: Block( statements), expressionBody: null))); } }