internal ListCSharpPattern(CSharpObjectPatternInfo info, LambdaExpression lengthAccess, LambdaExpression indexerAccess, ReadOnlyCollection <CSharpPattern> patterns) : base(info) { LengthAccess = lengthAccess; IndexerAccess = indexerAccess; Patterns = patterns; }
internal RecursiveCSharpPattern(CSharpObjectPatternInfo info, Type type, MethodInfo deconstructMethod, ReadOnlyCollection <PositionalCSharpSubpattern> deconstruction, ReadOnlyCollection <PropertyCSharpSubpattern> properties) : base(info) { Type = type; DeconstructMethod = deconstructMethod; Deconstruction = deconstruction; Properties = properties; }
/// <summary> /// Creates a pattern that always matches and assigns the input to a variable. /// </summary> /// <param name="info">Type information about the pattern.</param> /// <returns>A <see cref="DiscardCSharpPattern" /> that represents a pattern that always matches.</returns> public static VarCSharpPattern Var(CSharpObjectPatternInfo info) { RequiresNotNull(info, nameof(info)); if (info.Info.InputType != info.Info.NarrowedType) { throw Error.PatternInputAndNarrowedTypeShouldMatch(nameof(CSharpPatternType.Var)); } if (info.Variable != null && info.Variable.Type != info.Info.NarrowedType) { throw Error.CannotAssignPatternResultToVariable(info.Variable.Type, info.Info.NarrowedType); } return(new VarCSharpPattern(info)); }
/// <summary> /// Creates a positional pattern that matches on components of an object. /// </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 property subpatterns to apply.</param> /// <returns>A <see cref="IPositionalCSharpPattern" /> representing a positional pattern.</returns> public static IPositionalCSharpPattern Positional(CSharpObjectPatternInfo info, Type type, MethodInfo deconstructMethod, IEnumerable <PositionalCSharpSubpattern> deconstruction) { RequiresNotNull(info, nameof(info)); if (deconstructMethod == null && info.Variable == null && type == null) { var narrowedType = info.Info.NarrowedType; if (!IsTupleType(narrowedType)) { return(ITuple(deconstruction)); } } return(Recursive(info, type, deconstructMethod, deconstruction, properties: null)); }
// REVIEW: Type cannot be nullable. /// <summary> /// Creates a pattern that checks convertibility to a type and assigns to a variable. /// </summary> /// <param name="info">Type information about the pattern.</param> /// <param name="type">The type to check for.</param> /// <returns>A <see cref="DeclarationCSharpPattern" /> that represents a pattern that checks convertibility to a type and assigns to a variable.</returns> public static DeclarationCSharpPattern Declaration(CSharpObjectPatternInfo info, Type type) { RequiresNotNull(info, nameof(info)); RequiresNotNull(type, nameof(type)); ValidatePatternType(type); var variableType = info.Variable?.Type; if (variableType != null) { RequiresCompatiblePatternTypes(type, variableType); if (variableType != info.Info.NarrowedType) { throw Error.CannotAssignPatternResultToVariable(variableType, info.Info.NarrowedType); } } return(new DeclarationCSharpPattern(info, type)); }
/// <summary> /// Creates a list pattern. /// </summary> /// <param name="info">Type information about the pattern.</param> /// <param name="lengthAccess">The <see cref="LambdaExpression"/> representing the expression used to retrieve the collection's length or element count.</param> /// <param name="indexerAccess">The <see cref="LambdaExpression"/> representing the indexer access used to retrieve an element from the collection.</param> /// <param name="patterns">The list of <see cref="CSharpPattern"/> patterns to apply to the elements of the collection, optionally containing a slice pattern.</param> /// <returns>A <see cref="ListCSharpPattern" /> representing a list pattern.</returns> public static ListCSharpPattern List(CSharpObjectPatternInfo info, LambdaExpression lengthAccess, LambdaExpression indexerAccess, IEnumerable <CSharpPattern> patterns) { RequiresNotNull(info, nameof(info)); var nonNullInputType = info.Info.InputType.GetNonNullableType(); var collectionType = info.Info.NarrowedType; if (!AreEquivalent(nonNullInputType, collectionType)) { throw Error.ListPatternInputTypeInvalid(nonNullInputType, collectionType); } RequiresCanRead(lengthAccess, nameof(lengthAccess)); if (lengthAccess.Parameters.Count != 1) { throw Error.LengthAccessShouldHaveOneParameter(); } if (!AreEquivalent(lengthAccess.Parameters[0].Type, collectionType)) { throw Error.LengthAccessParameterShouldHaveCollectionType(collectionType); } if (lengthAccess.ReturnType != typeof(int)) { throw Error.LengthAccessShouldReturnInt32(); } RequiresCanRead(indexerAccess, nameof(indexerAccess)); if (indexerAccess.Parameters.Count != 2) { throw Error.IndexerAccessShouldHaveTwoParameters(); } if (!AreEquivalent(indexerAccess.Parameters[0].Type, collectionType)) { throw Error.IndexerAccessFirstParameterShouldHaveCollectionType(collectionType); } if (indexerAccess.Parameters[1].Type != typeof(Index)) { throw Error.IndexerAccessSecondParameterInvalidType(typeof(Index)); } var elementType = indexerAccess.ReturnType; if (elementType == typeof(void)) { throw Error.ElementTypeCannotBeVoid(); } var patternsList = patterns.ToArray(); RequiresNotNullItems(patternsList, nameof(patterns)); var hasSlice = false; for (int i = 0, n = patternsList.Length; i < n; i++) { var pattern = patternsList[i]; if (pattern.PatternType == CSharpPatternType.Slice) { if (hasSlice) { throw Error.MoreThanOneSlicePattern(); } hasSlice = true; RequiresCompatiblePatternTypes(collectionType, ref patternsList[i]); } else { RequiresCompatiblePatternTypes(elementType, ref patternsList[i]); } } return(new ListCSharpPattern(info, lengthAccess, indexerAccess, new TrueReadOnlyCollection <CSharpPattern>(patternsList))); }
/// <summary> /// Creates a list pattern. /// </summary> /// <param name="info">Type information about the pattern.</param> /// <param name="lengthAccess">The <see cref="LambdaExpression"/> representing the expression used to retrieve the collection's length or element count.</param> /// <param name="indexerAccess">The <see cref="LambdaExpression"/> representing the indexer access used to retrieve an element from the collection.</param> /// <param name="patterns">The list of <see cref="CSharpPattern"/> patterns to apply to the elements of the collection, optionally containing a slice pattern.</param> /// <returns>A <see cref="ListCSharpPattern" /> representing a list pattern.</returns> public static ListCSharpPattern List(CSharpObjectPatternInfo info, LambdaExpression lengthAccess, LambdaExpression indexerAccess, params CSharpPattern[] patterns) => List(info, lengthAccess, indexerAccess, (IEnumerable <CSharpPattern>)patterns);
/// <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); } }
/// <summary> /// Creates a property pattern that matches on properties. /// </summary> /// <param name="info">Type information about the pattern.</param> /// <param name="type">The type to check for.</param> /// <param name="properties">The property subpatterns to apply.</param> /// <returns>A <see cref="RecursiveCSharpPattern" /> representing a property pattern.</returns> public static RecursiveCSharpPattern Property(CSharpObjectPatternInfo info, Type type, IEnumerable <PropertyCSharpSubpattern> properties) => Recursive(info, type, deconstructMethod: null, deconstruction: null, properties);
/// <summary> /// Creates a property pattern that matches on properties. /// </summary> /// <param name="info">Type information about the pattern.</param> /// <param name="type">The type to check for.</param> /// <param name="properties">The property subpatterns to apply.</param> /// <returns>A <see cref="RecursiveCSharpPattern" /> representing a property pattern.</returns> public static RecursiveCSharpPattern Property(CSharpObjectPatternInfo info, Type type, params PropertyCSharpSubpattern[] properties) => Property(info, type, (IEnumerable <PropertyCSharpSubpattern>)properties);
// // TODO: Add more convenience overloads for positional recursive patterns. // // - Should it find Deconstruct instance methods if unspecified? // - This will require to find types from PositionalCSharpSubpattern.Pattern.InputType to do overload resolution for the out parameters. // /// <summary> /// Creates a positional pattern that matches on components of an object. /// </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 property subpatterns to apply.</param> /// <returns>A <see cref="IPositionalCSharpPattern" /> representing a positional pattern.</returns> public static IPositionalCSharpPattern Positional(CSharpObjectPatternInfo info, Type type, MethodInfo deconstructMethod, params PositionalCSharpSubpattern[] deconstruction) => Positional(info, type, deconstructMethod, (IEnumerable <PositionalCSharpSubpattern>)deconstruction);
internal CSharpObjectPattern(CSharpObjectPatternInfo info) : base(info.Info) { _objectInfo = info; }
internal VarCSharpPattern(CSharpObjectPatternInfo info) : base(info) { }
internal DeclarationCSharpPattern(CSharpObjectPatternInfo info, Type type) : base(info) { Type = type; }