Esempio n. 1
0
 protected XNode Visit(CSharpSubpattern node)
 {
     return(node switch
     {
         PositionalCSharpSubpattern p => Visit(p),
         PropertyCSharpSubpattern p => Visit(p),
         _ => throw ContractUtils.Unreachable,
     });
Esempio n. 2
0
 protected internal virtual PositionalCSharpSubpattern VisitPositionalSubpattern(PositionalCSharpSubpattern node) =>
 node.Update(
     VisitPattern(node.Pattern)
     );
Esempio n. 3
0
        /// <summary>
        /// Creates a recursive pattern that can perform positional matching and/or matching on properties.
        /// </summary>
        /// <param name="info">Type information about the pattern.</param>
        /// <param name="type">The type to check for.</param>
        /// <param name="deconstructMethod">The method used to deconstruct an object into its components for use with positional subpatterns.</param>
        /// <param name="deconstruction">The positional subpatterns to apply to the components of the object.</param>
        /// <param name="properties">The property subpatterns to apply.</param>
        /// <returns>A <see cref="RecursiveCSharpPattern" /> representing a recursive pattern.</returns>
        public static RecursiveCSharpPattern Recursive(CSharpObjectPatternInfo info, Type type, MethodInfo deconstructMethod, IEnumerable <PositionalCSharpSubpattern> deconstruction, IEnumerable <PropertyCSharpSubpattern> properties)
        {
            if (info == null)
            {
                // NB: We could arguably attempt to infer the type from the other parameters but that's a bit too implicit.

                RequiresNotNull(type, nameof(type));

                info = ObjectPatternInfo(PatternInfo(typeof(object), type), variable: null);
            }
            else
            {
                if (info.Variable != null)
                {
                    RequiresCompatiblePatternTypes(info.Info.NarrowedType, info.Variable.Type);
                }
            }

            var narrowedType             = info.Info.NarrowedType;
            var deconstructionCollection = deconstruction.ToReadOnly();
            var propertiesCollection     = properties.ToReadOnly();

            var objParam = Expression.Parameter(narrowedType); // NB: Utility to utilize some expression factories to expedite some checks.

            // REVIEW: Reordering of positional matches with names (for tuple elements or deconstruction out parameters) is not supported.

            validateType();
            validatePositional();
            validateProperties();

            return(new RecursiveCSharpPattern(info, type, deconstructMethod, deconstructionCollection, propertiesCollection));

            void validateType()
            {
                if (type != null)
                {
                    ValidatePatternType(type);

                    var variableType = info.Variable?.Type;

                    if (variableType != null)
                    {
                        RequiresCompatiblePatternTypes(type, variableType);

                        if (variableType != narrowedType)
                        {
                            throw Error.CannotAssignPatternResultToVariable(variableType, narrowedType);
                        }
                    }
                }
            }

            void validatePositional()
            {
                if (deconstructionCollection.Count > 0)
                {
                    foreach (var positionalPattern in deconstructionCollection)
                    {
                        RequiresNotNull(positionalPattern, nameof(deconstruction));
                    }

                    if (deconstructMethod != null)
                    {
                        validateWithDeconstructMethod();
                    }
                    else if (IsTupleType(narrowedType))
                    {
                        validateWithTupleType();
                    }
                    else
                    {
                        throw Error.InvalidPositionalPattern();
                    }

                    void validateWithDeconstructMethod()
                    {
                        ValidateMethodInfo(deconstructMethod);

                        if (deconstructMethod.ReturnType != typeof(void))
                        {
                            throw Error.DeconstructShouldReturnVoid(deconstructMethod);
                        }

                        var parameters = deconstructMethod.GetParametersCached();

                        if (deconstructMethod.IsStatic)
                        {
                            if (parameters.Length == 0)
                            {
                                throw Error.DeconstructExtensionMethodMissingThis(deconstructMethod);
                            }

                            ValidateOneArgument(deconstructMethod, ExpressionType.Call, objParam, parameters[0]);

                            parameters = parameters.RemoveFirst();
                        }
                        else
                        {
                            ValidateCallInstanceType(narrowedType, deconstructMethod);
                        }

                        var arity = parameters.Length;

                        checkArity(arity);

                        var parameterToPattern = new Dictionary <ParameterInfo, PositionalCSharpSubpattern>();

                        foreach (var positionalPattern in deconstructionCollection)
                        {
                            if (positionalPattern.Field != null)
                            {
                                throw Error.PositionalPatternWithDeconstructMethodCannotSpecifyField();
                            }

                            var parameter = positionalPattern.Parameter;

                            if (parameter != null)
                            {
                                if (parameter.Member != deconstructMethod)
                                {
                                    throw Error.PositionalPatternParameterIsNotDeclaredOnDeconstructMethod(parameter, deconstructMethod);
                                }

                                if (parameterToPattern.ContainsKey(parameter))
                                {
                                    throw Error.PositionalPatternParameterShouldOnlyBeUsedOnce(parameter);
                                }

                                parameterToPattern.Add(parameter, positionalPattern);
                            }
                        }

                        var bindByParameter = parameterToPattern.Count > 0;

                        if (bindByParameter && parameterToPattern.Count != arity)
                        {
                            throw Error.PositionalPatternWithDeconstructMethodShouldSpecifyAllParameters();
                        }

                        PositionalCSharpSubpattern getPositionalPattern(ParameterInfo parameter, int index) => bindByParameter
                            ? parameterToPattern[parameter]
                            : deconstructionCollection[index];

                        for (var i = 0; i < arity; i++)
                        {
                            var parameter = parameters[i];

                            if (!parameter.IsOut)
                            {
                                throw Error.DeconstructParameterShouldBeOut(parameter, deconstructMethod);
                            }

                            var pattern       = getPositionalPattern(parameter, i).Pattern;
                            var parameterType = parameter.ParameterType.GetElementType();

                            // REVIEW: Can we loosen the type checking here (assignment compatibility) or trigger ChangeType?
                            RequiresCompatiblePatternTypes(pattern.InputType, parameterType);
                        }
                    }

                    void validateWithTupleType()
                    {
                        var arity = GetTupleArity(narrowedType);

                        checkArity(arity);

                        var byIndexCount   = 0;
                        var indexToPattern = new PositionalCSharpSubpattern[arity];

                        foreach (var positionalPattern in deconstructionCollection)
                        {
                            if (positionalPattern.Parameter != null)
                            {
                                throw Error.PositionalPatternWithTupleCannotSpecifyParameter();
                            }

                            if (positionalPattern.Field != null)
                            {
                                var index = positionalPattern.Field.Index;

                                if (index < 0 || index >= arity)
                                {
                                    throw Error.PositionalPatternTupleIndexOutOfRange(index, arity);
                                }

                                if (indexToPattern[index] != null)
                                {
                                    throw Error.PositionalPatternTupleIndexShouldOnlyBeUsedOnce(index);
                                }

                                indexToPattern[index] = positionalPattern;
                                byIndexCount++;
                            }
                        }

                        var bindByIndex = byIndexCount > 0;

                        if (bindByIndex && byIndexCount != arity)
                        {
                            throw Error.PositionalPatternWithTupleShouldSpecifyAllIndices();
                        }

                        PositionalCSharpSubpattern getPositionalPattern(int index) => bindByIndex
                            ? indexToPattern[index]
                            : deconstructionCollection[index];

                        var elementTypes = GetTupleComponentTypes(narrowedType).ToReadOnly();

                        for (var i = 0; i < arity; i++)
                        {
                            var elementType = elementTypes[i];

                            var pattern = getPositionalPattern(i).Pattern;

                            // REVIEW: Can we loosen the type checking here (assignment compatibility) or trigger ChangeType?
                            RequiresCompatiblePatternTypes(pattern.InputType, elementType);
                        }
                    }

                    void checkArity(int arity)
                    {
                        if (arity != deconstructionCollection.Count)
                        {
                            throw Error.InvalidPositionalPatternCount(narrowedType);
                        }
                    }
                }
            }

            void validateProperties()
            {
                foreach (var propertyPattern in propertiesCollection)
                {
                    RequiresNotNull(propertyPattern, nameof(properties));

                    var member = findTopMostMember(propertyPattern.Member);

                    if (member.Member != null)
                    {
                        // TODO: Inline the check.
                        _ = Expression.MakeMemberAccess(objParam, member.Member);
                    }
                    else
                    {
                        // TODO: Inline the check.
                        _ = Helpers.GetTupleItemAccess(objParam, member.TupleField.Index);
                    }
                }