{"record":{"id":"24e28223e50b18a2","repo":"devlooped/moq","slug":"unsupported-expression-0-1","errorCode":null,"errorMessage":"Unsupported expression: {0}\n{1}","messagePattern":"Unsupported expression: (.+?)\n(.+?)","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Moq/ActionObserver.cs","lineNumber":83,"sourceCode":"                        if (resultType.IsAssignableFrom(body.Type) == false)\n                        {\n                            if (AwaitableFactory.TryGet(body.Type) is { } awaitableHandler\n                                && awaitableHandler.ResultType.IsAssignableFrom(resultType))\n                            {\n                                // We are here because the current invocation cannot be chained onto the previous one,\n                                // however it *can* be chained if we assume that there was a `.Result` query on the\n                                // former invocation that we don't see because non-virtual members aren't recorded.\n                                // In this case, we make things work by adding back the missing `.Result`:\n                                body = awaitableHandler.CreateResultExpression(body);\n                            }\n                        }\n                        body = Expression.Call(body, invocation.Method, GetArgumentExpressions(invocation, recorder.Matches.ToArray()));\n                    }\n                    else\n                    {\n                        // A recorder was set up, but it recorded no invocation. This means\n                        // that the invocation could not be intercepted:\n                        throw new ArgumentException(\n                            string.Format(\n                                CultureInfo.CurrentCulture,\n                                Resources.UnsupportedExpressionWithHint,\n                                $\"{actionParameterName} => {body.ToStringFixed()}...\",\n                                Resources.NextMemberNonInterceptable));\n                    }\n                }\n\n                // Now we've either got no error and a completely reconstructed expression, or\n                // we have an error and a partially reconstructed expression which we can use for\n                // diagnostic purposes:\n                if (error == null)\n                {\n                    return Expression.Lambda<Action<T>>(body.Apply(UpgradePropertyAccessorMethods.Rewriter), rootExpression);\n                }\n                else\n                {\n                    throw new ArgumentException(","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/devlooped/moq/blob/89a5be629c752960fe403e95ff583e4ae6f00542/src/Moq/ActionObserver.cs#L65-L101","documentation":"Moq's ActionObserver.ReconstructExpression rebuilds a LINQ expression for a delegate-based setup after the fact. When a recorder was attached to the action parameter but recorded no invocation, the intercepted call chain could not be reconstructed into a setup expression, so Moq throws ArgumentException with the unsupported expression plus a hint. This protects users from silently creating a setup that would never match.","triggerScenarios":"Calling a Mock.Of/observer-style API where the delegate passed to the action parameter never invoked any member on the mocked object — e.g. `mock.SetupAction(x => { }, ...)` or a lambda whose body touches nothing interceptable, so the recorder has zero recorded invocations when ReconstructExpression runs.","commonSituations":"Empty or no-op lambdas handed to delegate-based setups; lambdas invoking non-virtual/non-interceptable members; refactoring a lambda so the mock call is removed; conditional code paths where the mocked member call is behind an if that never executes.","solutions":["Ensure the lambda passed to the action parameter actually calls at least one member on the mock (e.g. `x => x.Save()` instead of `x => { }`).","Verify the invoked member is mockable (virtual, interface member, or overridable) so it can be intercepted by the recorder.","Read the hint appended after the expression (NextMemberNonInterceptable) and make the last member in the chain interceptable.","If the call cannot be made interceptable, switch to a conventional `mock.Setup(...)` on the interface/class directly instead of the expression-reconstruction API."],"exampleFix":"// before\nmock.SetupAction(x => { }); // no invocation recorded\n// after\nmock.SetupAction(x => x.Save());","handlingStrategy":"validation","validationCode":"// Before calling the observer/setup API, ensure the lambda invokes at least one member\nAction<MyMock> check = myMock;\nif (lambdaBodyContainsNoInvocation) throw new InvalidOperationException(\"Lambda must invoke a mockable member\");","typeGuard":"static bool HasInvocation(LambdaExpression e) =>\n    e.Body is MemberExpression || e.Body is MethodCallExpression || e.Body is InvocationExpression;","tryCatchPattern":"try\n{\n    mock.SetupAction(lambda);\n}\ncatch (ArgumentException ex) when (ex.Message.StartsWith(\"Unsupported expression\"))\n{\n    // fall back to explicit mock.Setup(...) or fix the lambda\n}","preventionTips":["Never pass empty or no-op lambdas to delegate-based setup APIs","Ensure every member invoked in the lambda is virtual/interface-based","Read the appended hint in the message to find the non-interceptable member","Prefer explicit mock.Setup(...) for non-trivial lambdas"],"tags":["moq","linq-expressions","argument-exception","setup"],"backgroundTag":"unsupported-operation","analyzedSha":"89a5be629c752960fe403e95ff583e4ae6f00542","analyzedAt":"2026-09-16T05:31:39.496Z","contentChangedAt":"2026-09-16T05:31:39.496Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}