/// <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); } }