/// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="RecordSchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="type" />
        /// is not an array or primitive type and <paramref name="schema" /> is a <see cref="RecordSchema" />;
        /// an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" /> otherwise.
        /// </returns>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema is RecordSchema recordSchema)
            {
                var underlying = Nullable.GetUnderlyingType(type) ?? type;

                if (!underlying.IsArray && !underlying.IsPrimitive)
                {
                    // since record deserialization is potentially recursive, create a top-level
                    // reference:
                    var parameter = Expression.Parameter(
                        Expression.GetDelegateType(context.Reader.Type.MakeByRefType(), underlying));

                    if (!context.References.TryGetValue((recordSchema, type), out var reference))
                    {
                        context.References.Add((recordSchema, type), reference = parameter);
                    }

                    // then build/set the delegate if it hasn’t been built yet:
                    if (parameter == reference)
                    {
                        Expression expression;

                        if (GetRecordConstructor(underlying, recordSchema) is ConstructorInfo constructor)
                        {
                            var parameters = constructor.GetParameters();

                            // map constructor parameters to fields:
                            var mapping = recordSchema.Fields
                                          .Select(field =>
                            {
                                // there will be a match or we wouldn’t have made it this far:
                                var match     = parameters.Single(parameter => IsMatch(field, parameter.Name));
                                var parameter = Expression.Parameter(match.ParameterType);

                                return(
                                    Match: match,
                                    Parameter: parameter,
                                    Assignment: (Expression)Expression.Assign(
                                        parameter,
                                        DeserializerBuilder.BuildExpression(match.ParameterType, field.Type, context)));
                            })
                                          .ToDictionary(r => r.Match, r => (r.Parameter, r.Assignment));

                            expression = Expression.Block(
                                mapping
                                .Select(d => d.Value.Parameter),
                                mapping
                                .Select(d => d.Value.Assignment)
                                .Concat(new[]
                            {
                                Expression.New(
                                    constructor,
                                    parameters
                                    .Select(parameter => mapping.ContainsKey(parameter)
                                                    ? (Expression)mapping[parameter].Parameter
                                                    : Expression.Constant(parameter.DefaultValue))),
                            }));
                        }
                        else
                        {
                            var members = underlying.GetMembers(MemberVisibility);

                            // support dynamic deserialization:
                            var value = Expression.Parameter(
                                underlying.IsAssignableFrom(typeof(ExpandoObject))
                                    ? typeof(ExpandoObject)
                                    : underlying);

                            expression = Expression.Block(
                                new[] { value },
                                new[] { (Expression)Expression.Assign(value, Expression.New(value.Type)) }
                                .Concat(recordSchema.Fields.Select(field =>
                            {
                                var match = members.SingleOrDefault(member => IsMatch(field, member));

                                Expression expression;

                                if (match == null)
                                {
                                    // always deserialize fields to advance the reader:
                                    expression = DeserializerBuilder.BuildExpression(typeof(object), field.Type, context);

                                    // fall back to a dynamic setter if the value supports it:
                                    if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(value.Type))
                                    {
                                        var flags  = CSharpBinderFlags.None;
                                        var infos  = new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) };
                                        var binder = Binder.SetMember(flags, field.Name, value.Type, infos);
                                        expression = Expression.Dynamic(binder, typeof(void), value, expression);
                                    }
                                }
                                else
                                {
                                    Expression inner;

                                    try
                                    {
                                        inner = DeserializerBuilder.BuildExpression(
                                            match switch
                                        {
                                            FieldInfo fieldMatch => fieldMatch.FieldType,
                                            PropertyInfo propertyMatch => propertyMatch.PropertyType,
                                            MemberInfo unknown => throw new InvalidOperationException($"Record fields can only be mapped to fields and properties."),
                                        },
                                            field.Type,
                                            context);
                                    }
                                    catch (Exception exception)
                                    {
                                        throw new UnsupportedTypeException(type, $"The {match.Name} member on {type} could not be mapped to the {field.Name} field on {recordSchema.FullName}.", exception);
                                    }

                                    expression = Expression.Assign(
                                        Expression.PropertyOrField(value, match.Name),
                                        inner);
                                }

                                return(expression);
                            }))