devlooped/moq · error · ArgumentException

Unsupported expression

Error message

Unsupported expression: {0}

What it means

ExpressionExtensions.Split decomposes a lambda expression into its member-access chain pieces (e.g. `x => x.A.B.C` into A, B, C). If, after stripping member accesses, the remainder is not a simple ParameterExpression (the lambda's root parameter), the expression is not a plain chain and Moq throws ArgumentException 'Unsupported expression: {0}'.

Solutions

  1. Rewrite the lambda as a pure member chain rooted at the parameter: `x => x.A.B`, no method calls, casts, operators, or null-conditionals.
  2. For method-call roots, use the appropriate Setup overload (e.g. `Setup(x => x.A())`) rather than Split-based APIs.
  3. Hoist conditional/default logic out of the expression: compute values outside and use `.Returns(...)` for them.
  4. Print the expression with ToStringFixed() from the message to identify which node breaks the chain.

Example fix

// before
mock.Setup(x => x.Bar?.Baz); // null-conditional -> unsupported
// after
mock.Setup(x => x.Bar.Baz);
Defensive patterns

Strategy: type-guard

Validate before calling

// Accept only plain parameter-rooted chains
static bool IsSplitSafe(LambdaExpression e) =>
    StripMembers(e.Body) is ParameterExpression;

Type guard

static bool IsMemberChain<T, R>(Expression<Func<T, R>> e) =>
    e.Body is MemberExpression me && StripToParameter(me) is ParameterExpression;

Try / catch

try
{
    var parts = expression.Split();
}
catch (ArgumentException ex) when (ex.Message.StartsWith("Unsupported expression"))
{
    // rewrite lambda as a plain chain or use a different Setup API
}

Prevention

When it happens

Trigger: Calling Split (via ToArray/Setup helpers) with a lambda whose body is not rooted at the parameter: indexers, method calls at the root, `x.A()`, casts, conversions, or expressions like `x => x.A.B ?? something`, `x => new Foo().Bar`, or a captured variable instead of the parameter.

Common situations: Passing compound expressions where a plain property chain is required; using `?.` (null-conditional) operators which compile to complex trees; boxing/cast wrappers `(IFoo)x.A`; setups written against local variables instead of the mock parameter.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of devlooped/moq@89a5be629c (2026-09-16). Data as JSON: /api/errors/294f8c9026247b0d. Report an issue: GitHub.

Appendix: source

Thrown at src/Moq/ExpressionExtensions.cs:164

        {
            Debug.Assert(expression != null);

            var parts = new Stack<MethodExpectation>();

            Expression remainder = expression.Body;
            while (CanSplit(remainder))
            {
                Split(remainder, out remainder, out var part, allowNonOverridableLastProperty: allowNonOverridableLastProperty && parts.Count == 0);
                parts.Push(part);
            }

            if (parts.Count > 0 && remainder is ParameterExpression)
            {
                return parts;
            }
            else
            {
                throw new ArgumentException(
                    string.Format(
                        CultureInfo.CurrentCulture,
                        Resources.UnsupportedExpression,
                        remainder.ToStringFixed()));
            }

            void Split(Expression e, out Expression r /* remainder */, out MethodExpectation p /* part */, bool assignment = false, bool allowNonOverridableLastProperty = false)
            {
                const string ParameterName = "...";

                switch (e.NodeType)
                {
                    case ExpressionType.Assign:          // assignment to a property or indexer
                    case ExpressionType.AddAssign:       // subscription of event handler to event
                    case ExpressionType.SubtractAssign:  // unsubscription of event handler from event
                        {
                            var assignmentExpression = (BinaryExpression)e;
                            Split(assignmentExpression.Left, out r, out var lhs, assignment: true);

View on GitHub (pinned to 89a5be629c)