From 1791abd0d9ddd3f4f5684bd25bd9ad5f1decfef9 Mon Sep 17 00:00:00 2001 From: Zoltan Varga Date: Mon, 8 Jan 2024 10:50:40 -0500 Subject: [PATCH 01/20] [mono] Fix passing of valuetypes with a non-8 byte aligned size on arm64+ios/macos. (#96079) * [mono] Fix passing of valuetypes with a non-8 byte aligned size on arm64+ios/macos. Part of the fix for https://github.com/dotnet/runtime/issues/96051. * Reenable tests on non-interpreter. * Fix. --- src/mono/mono/mini/mini-arm64.c | 46 ++++++++++++++++++++++++++++----- src/tests/issues.targets | 2 +- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/mono/mono/mini/mini-arm64.c b/src/mono/mono/mini/mini-arm64.c index bdd3d51a844ccc..b924f1be71f115 100644 --- a/src/mono/mono/mini/mini-arm64.c +++ b/src/mono/mono/mini/mini-arm64.c @@ -1677,7 +1677,8 @@ add_valuetype (CallInfo *cinfo, ArgInfo *ainfo, MonoType *t, gboolean is_return) } else { ainfo->nfregs_to_skip = FP_PARAM_REGS > cinfo->fr ? FP_PARAM_REGS - cinfo->fr : 0; cinfo->fr = FP_PARAM_REGS; - size = ALIGN_TO (size, 8); + if (!(ios_abi && cinfo->pinvoke)) + size = ALIGN_TO (size, 8); ainfo->storage = ArgVtypeOnStack; cinfo->stack_usage = ALIGN_TO (cinfo->stack_usage, align); ainfo->offset = cinfo->stack_usage; @@ -3240,16 +3241,49 @@ mono_arch_emit_outarg_vt (MonoCompile *cfg, MonoInst *ins, MonoInst *src) } break; } - case ArgVtypeOnStack: - for (i = 0; i < ainfo->size / 8; ++i) { - MONO_INST_NEW (cfg, load, OP_LOADI8_MEMBASE); + case ArgVtypeOnStack: { + int load_opcode = OP_LOADI8_MEMBASE; + int store_opcode = OP_STOREI8_MEMBASE_REG; + int size = 8; + int offset = 0; + while (offset < ainfo->size) { + int left = ainfo->size - offset; + if (left < 8) { + switch (left) { + case 7: + case 6: + case 5: + case 4: + load_opcode = OP_LOADI4_MEMBASE; + store_opcode = OP_STOREI4_MEMBASE_REG; + size = 4; + break; + case 3: + case 2: + load_opcode = OP_LOADI2_MEMBASE; + store_opcode = OP_STOREI2_MEMBASE_REG; + size = 2; + break; + case 1: + load_opcode = OP_LOADI1_MEMBASE; + store_opcode = OP_STOREI1_MEMBASE_REG; + size = 1; + break; + default: + g_assert_not_reached (); + break; + } + } + MONO_INST_NEW (cfg, load, load_opcode); load->dreg = mono_alloc_ireg (cfg); load->inst_basereg = src->dreg; - load->inst_offset = i * 8; + load->inst_offset = offset; MONO_ADD_INS (cfg->cbb, load); - MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI8_MEMBASE_REG, ARMREG_SP, ainfo->offset + (i * 8), load->dreg); + MONO_EMIT_NEW_STORE_MEMBASE (cfg, store_opcode, ARMREG_SP, ainfo->offset + offset, load->dreg); + offset += size; } break; + } case ArgInSIMDReg: MONO_INST_NEW (cfg, load, OP_LOADX_MEMBASE); load->dreg = mono_alloc_ireg (cfg); diff --git a/src/tests/issues.targets b/src/tests/issues.targets index ed013f8b2f52bc..e2405d7f043077 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -2758,7 +2758,7 @@ - + https://github.com/dotnet/runtime/issues/96051 From e9cd01ac0223bd0c844a7178ee6c6d41e8cd41a8 Mon Sep 17 00:00:00 2001 From: Zoltan Varga Date: Mon, 8 Jan 2024 10:54:46 -0500 Subject: [PATCH 02/20] [mono][aot] Allow valuetype sharing in wrappers for valuetypes with an explicit layout if the explicit size matches the computed size. (#96230) This happens for Vector64/Vector128. --- src/mono/mono/mini/mini-generic-sharing.c | 37 ++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/mono/mono/mini/mini-generic-sharing.c b/src/mono/mono/mini/mini-generic-sharing.c index 0c741ca48918ef..cf0a787cf5f953 100644 --- a/src/mono/mono/mini/mini-generic-sharing.c +++ b/src/mono/mono/mini/mini-generic-sharing.c @@ -1187,8 +1187,30 @@ get_wrapper_shared_vtype (MonoType *t) if (mono_class_has_failure (klass)) return NULL; - if (m_class_get_type_token (klass) && mono_metadata_packing_from_typedef (m_class_get_image (klass), m_class_get_type_token (klass), NULL, NULL)) - return NULL; + guint32 packing, packing_size; + gboolean has_explicit_size = FALSE; + if (m_class_get_type_token (klass) && mono_metadata_packing_from_typedef (m_class_get_image (klass), m_class_get_type_token (klass), &packing, &packing_size)) { + // FIXME: Support other sizes + if (packing == 0 && (packing_size == 8 || packing_size == 16)) { + has_explicit_size = TRUE; + switch (packing_size) { + case 8: + findex = 1; + args [0] = m_class_get_byval_arg (mono_get_int64_class ()); + break; + case 16: + findex = 2; + args [0] = m_class_get_byval_arg (mono_get_int64_class ()); + args [1] = m_class_get_byval_arg (mono_get_int64_class ()); + break; + default: + g_assert_not_reached (); + break; + } + } else { + return NULL; + } + } gpointer iter = NULL; MonoClassField *field; @@ -1199,9 +1221,11 @@ get_wrapper_shared_vtype (MonoType *t) if (m_class_is_byreflike (mono_class_from_mono_type_internal (ftype))) /* Cannot inflate generic params with byreflike types */ return NULL; - args [findex ++] = ftype; - if (findex >= 16) - break; + if (!has_explicit_size) { + args [findex ++] = ftype; + if (findex >= 16) + break; + } #ifdef TARGET_WASM if (ftype->type == MONO_TYPE_R4 || ftype->type == MONO_TYPE_R8 || MONO_TYPE_ISSTRUCT (ftype)) has_fp = TRUE; @@ -1209,10 +1233,9 @@ get_wrapper_shared_vtype (MonoType *t) } #ifdef TARGET_WASM - if (!has_fp) { + if (!has_explicit_size && !has_fp) { guint32 align; int size = mono_class_value_size (klass, &align); - /* Other platforms might pass small valuetypes or valuetypes with non-int fields differently */ if (align == 4 && size <= 4 * 5) { findex = size / align; From 1df91d391613d054a37a4123783caeca8931a38f Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Mon, 8 Jan 2024 17:30:02 +0100 Subject: [PATCH 03/20] Fix OperIsSimple assert in lower (#96615) --- src/coreclr/jit/lower.cpp | 11 ++- src/coreclr/jit/lower.h | 8 +-- src/coreclr/jit/lowerarmarch.cpp | 114 +++++++++++++++++-------------- 3 files changed, 73 insertions(+), 60 deletions(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 86285545cc07ce..0b82f2d09b8431 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -500,8 +500,8 @@ GenTree* Lowering::LowerNode(GenTree* node) case GT_NEG: #ifdef TARGET_ARM64 { - GenTree* next = TryLowerNegToMulLongOp(node->AsOp()); - if (next != nullptr) + GenTree* next; + if (TryLowerNegToMulLongOp(node->AsOp(), &next)) { return next; } @@ -6384,14 +6384,13 @@ GenTree* Lowering::LowerAdd(GenTreeOp* node) #ifdef TARGET_ARM64 if (node->OperIs(GT_ADD)) { - GenTree* next = LowerAddForPossibleContainment(node); - if (next != nullptr) + GenTree* next; + if (TryLowerAddForPossibleContainment(node, &next)) { return next; } - next = TryLowerAddSubToMulLongOp(node); - if (next != nullptr) + if (TryLowerAddSubToMulLongOp(node, &next)) { return next; } diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index fcde0e67292e8c..3f668c0f2d472a 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -88,14 +88,14 @@ class Lowering final : public Phase void ContainCheckLclHeap(GenTreeOp* node); void ContainCheckRet(GenTreeUnOp* ret); #ifdef TARGET_ARM64 - GenTree* TryLowerAndOrToCCMP(GenTreeOp* tree); + bool TryLowerAndOrToCCMP(GenTreeOp* tree, GenTree** next); insCflags TruthifyingFlags(GenCondition cond); void ContainCheckConditionalCompare(GenTreeCCMP* ccmp); void ContainCheckNeg(GenTreeOp* neg); void TryLowerCnsIntCselToCinc(GenTreeOp* select, GenTree* cond); void TryLowerCselToCSOp(GenTreeOp* select, GenTree* cond); - GenTree* TryLowerAddSubToMulLongOp(GenTreeOp* op); - GenTree* TryLowerNegToMulLongOp(GenTreeOp* op); + bool TryLowerAddSubToMulLongOp(GenTreeOp* op, GenTree** next); + bool TryLowerNegToMulLongOp(GenTreeOp* op, GenTree** next); #endif void ContainCheckSelect(GenTreeOp* select); void ContainCheckBitCast(GenTree* node); @@ -386,7 +386,7 @@ class Lowering final : public Phase bool IsValidConstForMovImm(GenTreeHWIntrinsic* node); void LowerHWIntrinsicFusedMultiplyAddScalar(GenTreeHWIntrinsic* node); void LowerModPow2(GenTree* node); - GenTree* LowerAddForPossibleContainment(GenTreeOp* node); + bool TryLowerAddForPossibleContainment(GenTreeOp* node, GenTree** next); #endif // !TARGET_XARCH && !TARGET_ARM64 GenTree* InsertNewSimdCreateScalarUnsafeNode(var_types type, GenTree* op1, diff --git a/src/coreclr/jit/lowerarmarch.cpp b/src/coreclr/jit/lowerarmarch.cpp index 20eea0e31e7a22..73bf503c46260a 100644 --- a/src/coreclr/jit/lowerarmarch.cpp +++ b/src/coreclr/jit/lowerarmarch.cpp @@ -526,8 +526,8 @@ GenTree* Lowering::LowerBinaryArithmetic(GenTreeOp* binOp) #ifdef TARGET_ARM64 if (binOp->OperIs(GT_AND, GT_OR)) { - GenTree* next = TryLowerAndOrToCCMP(binOp); - if (next != nullptr) + GenTree* next; + if (TryLowerAndOrToCCMP(binOp, &next)) { return next; } @@ -536,8 +536,8 @@ GenTree* Lowering::LowerBinaryArithmetic(GenTreeOp* binOp) if (binOp->OperIs(GT_SUB)) { // Attempt to optimize for umsubl/smsubl. - GenTree* next = TryLowerAddSubToMulLongOp(binOp); - if (next != nullptr) + GenTree* next; + if (TryLowerAddSubToMulLongOp(binOp, &next)) { return next; } @@ -957,25 +957,29 @@ void Lowering::LowerModPow2(GenTree* node) // // Arguments: // node - the node to lower +// next - [out] Next node to lower if this function returns true // -GenTree* Lowering::LowerAddForPossibleContainment(GenTreeOp* node) +// Return Value: +// false if no changes were made +// +bool Lowering::TryLowerAddForPossibleContainment(GenTreeOp* node, GenTree** next) { assert(node->OperIs(GT_ADD)); if (!comp->opts.OptimizationEnabled()) - return nullptr; + return false; if (node->isContained()) - return nullptr; + return false; if (!varTypeIsIntegral(node)) - return nullptr; + return false; if (node->gtFlags & GTF_SET_FLAGS) - return nullptr; + return false; if (node->gtOverflow()) - return nullptr; + return false; GenTree* op1 = node->gtGetOp1(); GenTree* op2 = node->gtGetOp2(); @@ -984,7 +988,7 @@ GenTree* Lowering::LowerAddForPossibleContainment(GenTreeOp* node) // then we do not want to risk moving it around // in this transformation. if (IsContainableImmed(node, op2)) - return nullptr; + return false; GenTree* mul = nullptr; GenTree* c = nullptr; @@ -1018,7 +1022,8 @@ GenTree* Lowering::LowerAddForPossibleContainment(GenTreeOp* node) ContainCheckNode(node); - return node->gtNext; + *next = node->gtNext; + return true; } // Transform "a * -b + c" to "c - a * b" else if (b->OperIs(GT_NEG) && !(b->gtFlags & GTF_SET_FLAGS) && !a->OperIs(GT_NEG) && !b->isContained() && @@ -1032,7 +1037,8 @@ GenTree* Lowering::LowerAddForPossibleContainment(GenTreeOp* node) ContainCheckNode(node); - return node->gtNext; + *next = node->gtNext; + return true; } // Transform "a * b + c" to "c + a * b" else if (op1->OperIs(GT_MUL)) @@ -1042,11 +1048,12 @@ GenTree* Lowering::LowerAddForPossibleContainment(GenTreeOp* node) ContainCheckNode(node); - return node->gtNext; + *next = node->gtNext; + return true; } } - return nullptr; + return false; } #endif @@ -2349,14 +2356,18 @@ void Lowering::ContainCheckCompare(GenTreeOp* cmp) // // Arguments: // tree - pointer to the node +// next - [out] Next node to lower if this function returns true +// +// Return Value: +// false if no changes were made // -GenTree* Lowering::TryLowerAndOrToCCMP(GenTreeOp* tree) +bool Lowering::TryLowerAndOrToCCMP(GenTreeOp* tree, GenTree** next) { assert(tree->OperIs(GT_AND, GT_OR)); if (!comp->opts.OptimizationEnabled()) { - return nullptr; + return false; } GenTree* op1 = tree->gtGetOp1(); @@ -2395,7 +2406,7 @@ GenTree* Lowering::TryLowerAndOrToCCMP(GenTreeOp* tree) { JITDUMP(" ..could not turn [%06u] or [%06u] into a def of flags, bailing\n", Compiler::dspTreeID(op1), Compiler::dspTreeID(op2)); - return nullptr; + return false; } BlockRange().Remove(op2); @@ -2437,7 +2448,8 @@ GenTree* Lowering::TryLowerAndOrToCCMP(GenTreeOp* tree) DISPTREERANGE(BlockRange(), tree); JITDUMP("\n"); - return tree->gtNext; + *next = tree->gtNext; + return true; } //------------------------------------------------------------------------ @@ -2781,32 +2793,33 @@ void Lowering::TryLowerCnsIntCselToCinc(GenTreeOp* select, GenTree* cond) // - One op is a MUL_LONG containing two integer operands, and the other is a long. // // Arguments: -// op - The ADD or SUB node to attempt an optimisation on. +// op - The ADD or SUB node to attempt an optimisation on. +// next - [out] Next node to lower if this function returns true // -// Returns: -// A pointer to the next node to evaluate. On no operation, returns nullptr. +// Return Value: +// false if no changes were made // -GenTree* Lowering::TryLowerAddSubToMulLongOp(GenTreeOp* op) +bool Lowering::TryLowerAddSubToMulLongOp(GenTreeOp* op, GenTree** next) { assert(op->OperIs(GT_ADD, GT_SUB)); if (!comp->opts.OptimizationEnabled()) - return nullptr; + return false; if (!comp->compOpportunisticallyDependsOn(InstructionSet_ArmBase_Arm64)) - return nullptr; + return false; if (op->isContained()) - return nullptr; + return false; if (!varTypeIsIntegral(op)) - return nullptr; + return false; if ((op->gtFlags & GTF_SET_FLAGS) != 0) - return nullptr; + return false; if (op->gtOverflow()) - return nullptr; + return false; GenTree* op1 = op->gtGetOp1(); GenTree* op2 = op->gtGetOp2(); @@ -2820,7 +2833,7 @@ GenTree* Lowering::TryLowerAddSubToMulLongOp(GenTreeOp* op) // addValue - (mulValue1 * mulValue2) if (op->OperIs(GT_SUB)) { - return nullptr; + return false; } mul = op1->AsOp(); @@ -2834,20 +2847,20 @@ GenTree* Lowering::TryLowerAddSubToMulLongOp(GenTreeOp* op) else { // Exit if neither operation are GT_MUL_LONG. - return nullptr; + return false; } // Additional value must be of long size. if (!addVal->TypeIs(TYP_LONG)) - return nullptr; + return false; // Mul values must both be integers. if (!genActualTypeIsInt(mul->gtOp1) || !genActualTypeIsInt(mul->gtOp2)) - return nullptr; + return false; // The multiply must evaluate to the same thing if moved. if (!IsInvariantInRange(mul, op)) - return nullptr; + return false; // Create the new node and replace the original. NamedIntrinsic intrinsicId = @@ -2870,14 +2883,13 @@ GenTree* Lowering::TryLowerAddSubToMulLongOp(GenTreeOp* op) BlockRange().Remove(mul); BlockRange().Remove(op); -#ifdef DEBUG JITDUMP("Converted to HW_INTRINSIC 'NI_ArmBase_Arm64_MultiplyLong[Add/Sub]'.\n"); JITDUMP(":\n"); DISPTREERANGE(BlockRange(), outOp); JITDUMP("\n"); -#endif - return outOp; + *next = outOp; + return true; } //---------------------------------------------------------------------------------------------- @@ -2887,44 +2899,45 @@ GenTree* Lowering::TryLowerAddSubToMulLongOp(GenTreeOp* op) // - op1 is a MUL_LONG containing two integer operands. // // Arguments: -// op - The NEG node to attempt an optimisation on. +// op - The NEG node to attempt an optimisation on. +// next - [out] Next node to lower if this function returns true // -// Returns: -// A pointer to the next node to evaluate. On no operation, returns nullptr. +// Return Value: +// false if no changes were made // -GenTree* Lowering::TryLowerNegToMulLongOp(GenTreeOp* op) +bool Lowering::TryLowerNegToMulLongOp(GenTreeOp* op, GenTree** next) { assert(op->OperIs(GT_NEG)); if (!comp->opts.OptimizationEnabled()) - return nullptr; + return false; if (!comp->compOpportunisticallyDependsOn(InstructionSet_ArmBase_Arm64)) - return nullptr; + return false; if (op->isContained()) - return nullptr; + return false; if (!varTypeIsIntegral(op)) - return nullptr; + return false; if ((op->gtFlags & GTF_SET_FLAGS) != 0) - return nullptr; + return false; GenTree* op1 = op->gtGetOp1(); // Ensure the negated operand is a MUL_LONG. if (!op1->OperIs(GT_MUL_LONG)) - return nullptr; + return false; // Ensure the MUL_LONG contains two integer parameters. GenTreeOp* mul = op1->AsOp(); if (!genActualTypeIsInt(mul->gtOp1) || !genActualTypeIsInt(mul->gtOp2)) - return nullptr; + return false; // The multiply must evaluate to the same thing if evaluated at 'op'. if (!IsInvariantInRange(mul, op)) - return nullptr; + return false; // Able to optimise, create the new node and replace the original. GenTreeHWIntrinsic* outOp = @@ -2953,7 +2966,8 @@ GenTree* Lowering::TryLowerNegToMulLongOp(GenTreeOp* op) JITDUMP("\n"); #endif - return outOp; + *next = outOp; + return true; } #endif // TARGET_ARM64 From 8ffc96c85d450e1793380426968e6bc68b2a23f2 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 8 Jan 2024 11:32:01 -0500 Subject: [PATCH 04/20] Revamp empty special-casing in LINQ (#96602) * Revamp empty special-casing in LINQ Enumerable.Empty used to return Array.Empty. Towards the beginning of .NET Core, LINQ was imbued with an internal "partition" concept for flowing more information around between operators, and as part of that, Empty was changed to return a singleton instance of a specialized partition implementation. The upside of this was that methods typed to return IPartition could return the same singleton as Empty. There are multiple downsides, however. For one, the whole IPartition concept is only built into a "speed-optimized" build of LINQ; builds that care more about size (e.g. browser) end up not having it, and thus Empty there ends up being Array.Empty, such that a different type ends up being returned based on the build, which is not ideal. Further, any paths that check for empty now effectively have two things to check for: the empty partition or an empty array, making those checks more expensive, if they're even done at all, or in some cases missing out on possible optimization. This is more pronounced today, now that `[]` with collection expressions will produce Array.Empty, and it'd be really nice if there wasn't a difference between Enumerable.Empty and `[]` assigned to `IEnumerable`. This change puts Enumerable.Empty back to always being Array.Empty. The internal IPartition-based APIs that drove us to need the EmptyPartition are changed to just use null as an indication of empty. Places we were already checking for `is EmptyPartition` are changed to check for an empty array (if they weren't already), and other APIs that weren't checking at all now have a check if it makes sense to do so (I audited all of the APIs, and didn't include checks in ones where it could meaningfully affect semantics, e.g. a fast path that might cause us not to get an enumerator from a secondary enumerable input). * Rename IsImmutableEmpty to IsEmptyArray per PR feedback * Remove bogus PLINQ tests The tests were validating the underlying type of the operator returned for Concat, which is not material. * Fix Skip special-casing The check needs to be moved to before the count <= 0 check... otherwise calling Skip on an empty array with a count of 0 would still allocate an iterator. --- .../tests/QueryOperators/AsEnumerableTests.cs | 1 - .../tests/QueryOperators/AsSequentialTests.cs | 1 - .../System.Linq/src/System.Linq.csproj | 2 - .../src/System/Linq/AggregateBy.cs | 10 ++ .../System.Linq/src/System/Linq/Chunk.cs | 7 +- .../src/System/Linq/Concat.SpeedOpt.cs | 2 +- .../System.Linq/src/System/Linq/Concat.cs | 10 ++ .../System.Linq/src/System/Linq/CountBy.cs | 5 + .../System.Linq/src/System/Linq/DebugView.cs | 10 +- .../System/Linq/DefaultIfEmpty.SpeedOpt.cs | 2 +- .../System.Linq/src/System/Linq/Distinct.cs | 10 ++ .../src/System/Linq/Enumerable.SizeOpt.cs | 17 -- .../src/System/Linq/Enumerable.SpeedOpt.cs | 17 -- .../System.Linq/src/System/Linq/Enumerable.cs | 13 ++ .../System.Linq/src/System/Linq/GroupJoin.cs | 34 +--- .../System.Linq/src/System/Linq/Grouping.cs | 164 +++++++++++------- .../System.Linq/src/System/Linq/IPartition.cs | 10 +- .../System.Linq/src/System/Linq/Index.cs | 5 + .../System.Linq/src/System/Linq/Join.cs | 34 +--- .../System.Linq/src/System/Linq/Last.cs | 2 +- .../src/System/Linq/Lookup.SpeedOpt.cs | 2 +- .../System.Linq/src/System/Linq/Lookup.cs | 36 ++-- .../System/Linq/OrderedEnumerable.SpeedOpt.cs | 4 +- .../src/System/Linq/Partition.SpeedOpt.cs | 114 +----------- .../src/System/Linq/Range.SpeedOpt.cs | 4 +- .../System.Linq/src/System/Linq/Range.cs | 2 +- .../src/System/Linq/Repeat.SpeedOpt.cs | 4 +- .../System.Linq/src/System/Linq/Repeat.cs | 2 +- .../System.Linq/src/System/Linq/Reverse.cs | 5 + .../src/System/Linq/Select.SpeedOpt.cs | 32 ++-- .../System.Linq/src/System/Linq/Select.cs | 19 +- .../System.Linq/src/System/Linq/SelectMany.cs | 20 +++ .../System.Linq/src/System/Linq/Skip.cs | 23 ++- .../src/System/Linq/Take.SpeedOpt.cs | 19 +- .../System.Linq/src/System/Linq/Take.cs | 33 +++- .../src/System/Linq/ThrowHelper.cs | 4 +- .../System.Linq/src/System/Linq/Where.cs | 14 +- .../System.Linq/tests/EmptyEnumerable.cs | 8 +- .../System.Linq/tests/EmptyPartitionTests.cs | 135 -------------- .../tests/System.Linq.Tests.csproj | 1 - 40 files changed, 350 insertions(+), 487 deletions(-) delete mode 100644 src/libraries/System.Linq/src/System/Linq/Enumerable.SizeOpt.cs delete mode 100644 src/libraries/System.Linq/src/System/Linq/Enumerable.SpeedOpt.cs delete mode 100644 src/libraries/System.Linq/tests/EmptyPartitionTests.cs diff --git a/src/libraries/System.Linq.Parallel/tests/QueryOperators/AsEnumerableTests.cs b/src/libraries/System.Linq.Parallel/tests/QueryOperators/AsEnumerableTests.cs index d598da2fd0afd0..962d8745b23892 100644 --- a/src/libraries/System.Linq.Parallel/tests/QueryOperators/AsEnumerableTests.cs +++ b/src/libraries/System.Linq.Parallel/tests/QueryOperators/AsEnumerableTests.cs @@ -57,7 +57,6 @@ public static void AsEnumerable_LinqBinding(Labeled> labeled, Assert.IsNotType>(enumerable.Cast()); Assert.True(enumerable.Cast() is ParallelQuery); - Assert.False(enumerable.Concat(Enumerable.Range(0, count)) is ParallelQuery); Assert.False(enumerable.DefaultIfEmpty() is ParallelQuery); Assert.False(enumerable.Distinct() is ParallelQuery); Assert.False(enumerable.Except(Enumerable.Range(0, count)) is ParallelQuery); diff --git a/src/libraries/System.Linq.Parallel/tests/QueryOperators/AsSequentialTests.cs b/src/libraries/System.Linq.Parallel/tests/QueryOperators/AsSequentialTests.cs index ec757e9d8e93d0..6eada873e8bda9 100644 --- a/src/libraries/System.Linq.Parallel/tests/QueryOperators/AsSequentialTests.cs +++ b/src/libraries/System.Linq.Parallel/tests/QueryOperators/AsSequentialTests.cs @@ -57,7 +57,6 @@ public static void AsSequential_LinqBinding(Labeled> labeled, Assert.IsNotType>(seq.Cast()); Assert.True(seq.Cast() is ParallelQuery); - Assert.False(seq.Concat(Enumerable.Range(0, count)) is ParallelQuery); Assert.False(seq.DefaultIfEmpty() is ParallelQuery); Assert.False(seq.Distinct() is ParallelQuery); Assert.False(seq.Except(Enumerable.Range(0, count)) is ParallelQuery); diff --git a/src/libraries/System.Linq/src/System.Linq.csproj b/src/libraries/System.Linq/src/System.Linq.csproj index 8ccb640b191078..6e810da322b40f 100644 --- a/src/libraries/System.Linq/src/System.Linq.csproj +++ b/src/libraries/System.Linq/src/System.Linq.csproj @@ -12,7 +12,6 @@ - - diff --git a/src/libraries/System.Linq/src/System/Linq/AggregateBy.cs b/src/libraries/System.Linq/src/System/Linq/AggregateBy.cs index 4f6c1a777a666e..e73b646ca403ca 100644 --- a/src/libraries/System.Linq/src/System/Linq/AggregateBy.cs +++ b/src/libraries/System.Linq/src/System/Linq/AggregateBy.cs @@ -28,6 +28,11 @@ public static IEnumerable> AggregateBy> AggregateBy Chunk(this IEnumerable so ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.size); } + if (IsEmptyArray(source)) + { + return []; + } + return ChunkIterator(source, size); } diff --git a/src/libraries/System.Linq/src/System/Linq/Concat.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Concat.SpeedOpt.cs index 70dfe8d5c230da..1df7809baa65c6 100644 --- a/src/libraries/System.Linq/src/System/Linq/Concat.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Concat.SpeedOpt.cs @@ -150,7 +150,7 @@ private TSource[] PreallocatingToArray() if (count == 0) { - return Array.Empty(); + return []; } var array = new TSource[count]; diff --git a/src/libraries/System.Linq/src/System/Linq/Concat.cs b/src/libraries/System.Linq/src/System/Linq/Concat.cs index 3592f0493ab0bc..e57efd1a047d99 100644 --- a/src/libraries/System.Linq/src/System/Linq/Concat.cs +++ b/src/libraries/System.Linq/src/System/Linq/Concat.cs @@ -20,6 +20,16 @@ public static IEnumerable Concat(this IEnumerable fir ThrowHelper.ThrowArgumentNullException(ExceptionArgument.second); } + if (IsEmptyArray(first)) + { + return second; + } + + if (IsEmptyArray(second)) + { + return first; + } + return first is ConcatIterator firstConcat ? firstConcat.Concat(second) : new Concat2Iterator(first, second); diff --git a/src/libraries/System.Linq/src/System/Linq/CountBy.cs b/src/libraries/System.Linq/src/System/Linq/CountBy.cs index dcd93c5567cd1d..bc03e9adafa310 100644 --- a/src/libraries/System.Linq/src/System/Linq/CountBy.cs +++ b/src/libraries/System.Linq/src/System/Linq/CountBy.cs @@ -19,6 +19,11 @@ public static IEnumerable> CountBy(this I ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); } + if (IsEmptyArray(source)) + { + return []; + } + return CountByIterator(source, keySelector, keyComparer); } diff --git a/src/libraries/System.Linq/src/System/Linq/DebugView.cs b/src/libraries/System.Linq/src/System/Linq/DebugView.cs index 284c0e6cf9e6c9..ddfb49cc3e2f9c 100644 --- a/src/libraries/System.Linq/src/System/Linq/DebugView.cs +++ b/src/libraries/System.Linq/src/System/Linq/DebugView.cs @@ -76,11 +76,7 @@ public object?[] Items { get { - var tempList = new List(); - foreach (object? item in _enumerable) - { - tempList.Add(item); - } + List tempList = [.. _enumerable]; if (tempList.Count == 0) { @@ -114,10 +110,10 @@ public SystemLinq_GroupingDebugView(Grouping grouping) internal sealed class SystemLinq_LookupDebugView { - private readonly Lookup _lookup; + private readonly ILookup _lookup; private IGrouping[]? _cachedGroupings; - public SystemLinq_LookupDebugView(Lookup lookup) + public SystemLinq_LookupDebugView(ILookup lookup) { _lookup = lookup; } diff --git a/src/libraries/System.Linq/src/System/Linq/DefaultIfEmpty.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/DefaultIfEmpty.SpeedOpt.cs index 4bda8b86cc1753..24619cc438136b 100644 --- a/src/libraries/System.Linq/src/System/Linq/DefaultIfEmpty.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/DefaultIfEmpty.SpeedOpt.cs @@ -13,7 +13,7 @@ private sealed partial class DefaultIfEmptyIterator : IIListProvider ToList() diff --git a/src/libraries/System.Linq/src/System/Linq/Distinct.cs b/src/libraries/System.Linq/src/System/Linq/Distinct.cs index 173ac951e96dca..e41973a46ec116 100644 --- a/src/libraries/System.Linq/src/System/Linq/Distinct.cs +++ b/src/libraries/System.Linq/src/System/Linq/Distinct.cs @@ -17,6 +17,11 @@ public static IEnumerable Distinct(this IEnumerable s ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } + if (IsEmptyArray(source)) + { + return []; + } + return new DistinctIterator(source, comparer); } @@ -56,6 +61,11 @@ public static IEnumerable DistinctBy(this IEnumerable Empty() => Array.Empty(); - - private static TResult[]? GetEmptyIfEmpty(IEnumerable source) => - source is TSource[] { Length: 0 } ? - Array.Empty() : - null; - } -} diff --git a/src/libraries/System.Linq/src/System/Linq/Enumerable.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Enumerable.SpeedOpt.cs deleted file mode 100644 index cc74d0d0bdb252..00000000000000 --- a/src/libraries/System.Linq/src/System/Linq/Enumerable.SpeedOpt.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; - -namespace System.Linq -{ - public static partial class Enumerable - { - public static IEnumerable Empty() => EmptyPartition.Instance; - - private static IEnumerable? GetEmptyIfEmpty(IEnumerable source) => - source is EmptyPartition ? - EmptyPartition.Instance : - null; - } -} diff --git a/src/libraries/System.Linq/src/System/Linq/Enumerable.cs b/src/libraries/System.Linq/src/System/Linq/Enumerable.cs index 029583b8f4b346..887a3800683124 100644 --- a/src/libraries/System.Linq/src/System/Linq/Enumerable.cs +++ b/src/libraries/System.Linq/src/System/Linq/Enumerable.cs @@ -11,6 +11,19 @@ public static partial class Enumerable { public static IEnumerable AsEnumerable(this IEnumerable source) => source; + /// Returns an empty . + public static IEnumerable Empty() => + Array.Empty(); // explicitly not using [] in case the compiler ever changed to using Enumerable.Empty + + /// Gets whether the enumerable is an empty array + /// + /// If is ever changed to return something other than an empty array, + /// this helper should also be updated to return true for that in addition to for an empty array. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsEmptyArray(IEnumerable source) => + source is TSource[] { Length: 0 }; + /// /// Sets the 's to be /// and returns the relevant portion of the list's backing array as a span. diff --git a/src/libraries/System.Linq/src/System/Linq/GroupJoin.cs b/src/libraries/System.Linq/src/System/Linq/GroupJoin.cs index 71175805b97fc6..d52a2d3740d29d 100644 --- a/src/libraries/System.Linq/src/System/Linq/GroupJoin.cs +++ b/src/libraries/System.Linq/src/System/Linq/GroupJoin.cs @@ -7,7 +7,10 @@ namespace System.Linq { public static partial class Enumerable { - public static IEnumerable GroupJoin(this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func, TResult> resultSelector) + public static IEnumerable GroupJoin(this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func, TResult> resultSelector) => + GroupJoin(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer: null); + + public static IEnumerable GroupJoin(this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func, TResult> resultSelector, IEqualityComparer? comparer) { if (outer == null) { @@ -34,34 +37,9 @@ public static IEnumerable GroupJoin(this ThrowHelper.ThrowArgumentNullException(ExceptionArgument.resultSelector); } - return GroupJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, null); - } - - public static IEnumerable GroupJoin(this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func, TResult> resultSelector, IEqualityComparer? comparer) - { - if (outer == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.outer); - } - - if (inner == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.inner); - } - - if (outerKeySelector == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.outerKeySelector); - } - - if (innerKeySelector == null) + if (IsEmptyArray(outer)) { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.innerKeySelector); - } - - if (resultSelector == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.resultSelector); + return []; } return GroupJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); diff --git a/src/libraries/System.Linq/src/System/Linq/Grouping.cs b/src/libraries/System.Linq/src/System/Linq/Grouping.cs index b12629ffc8e2f2..958642f624d0d3 100644 --- a/src/libraries/System.Linq/src/System/Linq/Grouping.cs +++ b/src/libraries/System.Linq/src/System/Linq/Grouping.cs @@ -10,28 +10,116 @@ namespace System.Linq public static partial class Enumerable { public static IEnumerable> GroupBy(this IEnumerable source, Func keySelector) => - new GroupedEnumerable(source, keySelector, null); + GroupBy(source, keySelector, comparer: null); - public static IEnumerable> GroupBy(this IEnumerable source, Func keySelector, IEqualityComparer? comparer) => - new GroupedEnumerable(source, keySelector, comparer); + public static IEnumerable> GroupBy(this IEnumerable source, Func keySelector, IEqualityComparer? comparer) + { + if (source is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + if (keySelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); + } + + if (IsEmptyArray(source)) + { + return []; + } + + return new GroupedEnumerable(source, keySelector, comparer); + } public static IEnumerable> GroupBy(this IEnumerable source, Func keySelector, Func elementSelector) => - new GroupedEnumerable(source, keySelector, elementSelector, null); + GroupBy(source, keySelector, elementSelector, comparer: null); + + public static IEnumerable> GroupBy(this IEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer? comparer) + { + if (source is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + if (keySelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); + } + + if (elementSelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.elementSelector); + } + + if (IsEmptyArray(source)) + { + return []; + } - public static IEnumerable> GroupBy(this IEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer? comparer) => - new GroupedEnumerable(source, keySelector, elementSelector, comparer); + return new GroupedEnumerable(source, keySelector, elementSelector, comparer); + } public static IEnumerable GroupBy(this IEnumerable source, Func keySelector, Func, TResult> resultSelector) => - new GroupedResultEnumerable(source, keySelector, resultSelector, null); + GroupBy(source, keySelector, resultSelector, comparer: null); + + public static IEnumerable GroupBy(this IEnumerable source, Func keySelector, Func, TResult> resultSelector, IEqualityComparer? comparer) + { + if (source is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + if (keySelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); + } + + if (resultSelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.resultSelector); + } + + if (IsEmptyArray(source)) + { + return []; + } + + return new GroupedResultEnumerable(source, keySelector, resultSelector, comparer); + } public static IEnumerable GroupBy(this IEnumerable source, Func keySelector, Func elementSelector, Func, TResult> resultSelector) => - new GroupedResultEnumerable(source, keySelector, elementSelector, resultSelector, null); + GroupBy(source, keySelector, elementSelector, resultSelector, comparer: null); + + public static IEnumerable GroupBy(this IEnumerable source, Func keySelector, Func elementSelector, Func, TResult> resultSelector, IEqualityComparer? comparer) + { + if (source is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + if (keySelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); + } - public static IEnumerable GroupBy(this IEnumerable source, Func keySelector, Func, TResult> resultSelector, IEqualityComparer? comparer) => - new GroupedResultEnumerable(source, keySelector, resultSelector, comparer); + if (elementSelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.elementSelector); + } - public static IEnumerable GroupBy(this IEnumerable source, Func keySelector, Func elementSelector, Func, TResult> resultSelector, IEqualityComparer? comparer) => - new GroupedResultEnumerable(source, keySelector, elementSelector, resultSelector, comparer); + if (resultSelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.resultSelector); + } + + if (IsEmptyArray(source)) + { + return []; + } + + return new GroupedResultEnumerable(source, keySelector, elementSelector, resultSelector, comparer); + } } public interface IGrouping : IEnumerable @@ -150,23 +238,6 @@ internal sealed partial class GroupedResultEnumerable source, Func keySelector, Func elementSelector, Func, TResult> resultSelector, IEqualityComparer? comparer) { - if (source is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); - } - if (keySelector is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); - } - if (elementSelector is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.elementSelector); - } - if (resultSelector is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.resultSelector); - } - _source = source; _keySelector = keySelector; _elementSelector = elementSelector; @@ -192,19 +263,6 @@ internal sealed partial class GroupedResultEnumerable : public GroupedResultEnumerable(IEnumerable source, Func keySelector, Func, TResult> resultSelector, IEqualityComparer? comparer) { - if (source is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); - } - if (keySelector is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); - } - if (resultSelector is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.resultSelector); - } - _source = source; _keySelector = keySelector; _resultSelector = resultSelector; @@ -229,19 +287,6 @@ internal sealed partial class GroupedEnumerable : IEnum public GroupedEnumerable(IEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer? comparer) { - if (source is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); - } - if (keySelector is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); - } - if (elementSelector is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.elementSelector); - } - _source = source; _keySelector = keySelector; _elementSelector = elementSelector; @@ -262,15 +307,6 @@ internal sealed partial class GroupedEnumerable : IEnumerable source, Func keySelector, IEqualityComparer? comparer) { - if (source is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); - } - if (keySelector is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); - } - _source = source; _keySelector = keySelector; _comparer = comparer; diff --git a/src/libraries/System.Linq/src/System/Linq/IPartition.cs b/src/libraries/System.Linq/src/System/Linq/IPartition.cs index 68b676f0948fa6..86db1921b12f61 100644 --- a/src/libraries/System.Linq/src/System/Linq/IPartition.cs +++ b/src/libraries/System.Linq/src/System/Linq/IPartition.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; - namespace System.Linq { /// @@ -14,15 +12,15 @@ internal interface IPartition : IIListProvider /// Creates a new partition that skips the specified number of elements from this sequence. /// /// The number of elements to skip. - /// An with the first items removed. - IPartition Skip(int count); + /// An with the first items removed, or null if known empty. + IPartition? Skip(int count); /// /// Creates a new partition that takes the specified number of elements from this sequence. /// /// The number of elements to take. - /// An with only the first items. - IPartition Take(int count); + /// An with only the first items, or null if known empty. + IPartition? Take(int count); /// /// Gets the item associated with a 0-based index in this sequence. diff --git a/src/libraries/System.Linq/src/System/Linq/Index.cs b/src/libraries/System.Linq/src/System/Linq/Index.cs index 437f02370ac4a0..b9d0e6e0bb86bf 100644 --- a/src/libraries/System.Linq/src/System/Linq/Index.cs +++ b/src/libraries/System.Linq/src/System/Linq/Index.cs @@ -18,6 +18,11 @@ public static partial class Enumerable ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } + if (IsEmptyArray(source)) + { + return []; + } + return IndexIterator(source); } diff --git a/src/libraries/System.Linq/src/System/Linq/Join.cs b/src/libraries/System.Linq/src/System/Linq/Join.cs index 82618ed237503b..b1c56e01725a00 100644 --- a/src/libraries/System.Linq/src/System/Linq/Join.cs +++ b/src/libraries/System.Linq/src/System/Linq/Join.cs @@ -7,7 +7,10 @@ namespace System.Linq { public static partial class Enumerable { - public static IEnumerable Join(this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector) + public static IEnumerable Join(this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector) => + Join(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer: null); + + public static IEnumerable Join(this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector, IEqualityComparer? comparer) { if (outer == null) { @@ -34,34 +37,9 @@ public static IEnumerable Join(this IEnu ThrowHelper.ThrowArgumentNullException(ExceptionArgument.resultSelector); } - return JoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, null); - } - - public static IEnumerable Join(this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector, IEqualityComparer? comparer) - { - if (outer == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.outer); - } - - if (inner == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.inner); - } - - if (outerKeySelector == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.outerKeySelector); - } - - if (innerKeySelector == null) + if (IsEmptyArray(outer)) { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.innerKeySelector); - } - - if (resultSelector == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.resultSelector); + return []; } return JoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); diff --git a/src/libraries/System.Linq/src/System/Linq/Last.cs b/src/libraries/System.Linq/src/System/Linq/Last.cs index cebe4d09328879..568f0d8670faf8 100644 --- a/src/libraries/System.Linq/src/System/Linq/Last.cs +++ b/src/libraries/System.Linq/src/System/Linq/Last.cs @@ -57,7 +57,7 @@ public static TSource LastOrDefault(this IEnumerable source, T /// or is . public static TSource LastOrDefault(this IEnumerable source, Func predicate, TSource defaultValue) { - var last = source.TryGetLast(predicate, out bool found); + TSource? last = source.TryGetLast(predicate, out bool found); return found ? last! : defaultValue; } diff --git a/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs index 2ef86a008493a8..16a9d7e0a3f4a0 100644 --- a/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs @@ -18,7 +18,7 @@ IGrouping[] IIListProvider>.ToArray() } else { - array = Array.Empty>(); + array = []; } return array; } diff --git a/src/libraries/System.Linq/src/System/Linq/Lookup.cs b/src/libraries/System.Linq/src/System/Linq/Lookup.cs index 4e40e9c2e695d2..055bf6c61018bd 100644 --- a/src/libraries/System.Linq/src/System/Linq/Lookup.cs +++ b/src/libraries/System.Linq/src/System/Linq/Lookup.cs @@ -10,7 +10,7 @@ namespace System.Linq public static partial class Enumerable { public static ILookup ToLookup(this IEnumerable source, Func keySelector) => - ToLookup(source, keySelector, null); + ToLookup(source, keySelector, comparer: null); public static ILookup ToLookup(this IEnumerable source, Func keySelector, IEqualityComparer? comparer) { @@ -24,11 +24,16 @@ public static ILookup ToLookup(this IEnumerable.Instance; + } + return Lookup.Create(source, keySelector, comparer); } public static ILookup ToLookup(this IEnumerable source, Func keySelector, Func elementSelector) => - ToLookup(source, keySelector, elementSelector, null); + ToLookup(source, keySelector, elementSelector, comparer: null); public static ILookup ToLookup(this IEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer? comparer) { @@ -47,6 +52,11 @@ public static ILookup ToLookup(this IEn ThrowHelper.ThrowArgumentNullException(ExceptionArgument.elementSelector); } + if (IsEmptyArray(source)) + { + return EmptyLookup.Instance; + } + return Lookup.Create(source, keySelector, elementSelector, comparer); } } @@ -121,14 +131,7 @@ private Lookup(IEqualityComparer? comparer) public int Count => _count; - public IEnumerable this[TKey key] - { - get - { - Grouping? grouping = GetGrouping(key, create: false); - return grouping ?? Enumerable.Empty(); - } - } + public IEnumerable this[TKey key] => GetGrouping(key, create: false) ?? Enumerable.Empty(); public bool Contains(TKey key) => GetGrouping(key, create: false) != null; @@ -255,4 +258,17 @@ private void Resize() _groupings = newGroupings; } } + + [DebuggerDisplay("Count = 0")] + [DebuggerTypeProxy(typeof(SystemLinq_LookupDebugView<,>))] + internal sealed class EmptyLookup : ILookup + { + public static readonly EmptyLookup Instance = new(); + + public IEnumerable this[TKey key] => []; + public int Count => 0; + public bool Contains(TKey key) => false; + public IEnumerator> GetEnumerator() => Enumerable.Empty>().GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } } diff --git a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs index e9cb982c2bc43c..1e6f367bb37a85 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs @@ -62,7 +62,7 @@ internal TElement[] ToArray(int minIdx, int maxIdx) int count = buffer._count; if (count <= minIdx) { - return Array.Empty(); + return []; } if (count <= maxIdx) @@ -72,7 +72,7 @@ internal TElement[] ToArray(int minIdx, int maxIdx) if (minIdx == maxIdx) { - return new TElement[] { GetEnumerableSorter().ElementAt(buffer._items, count, minIdx) }; + return [GetEnumerableSorter().ElementAt(buffer._items, count, minIdx)]; } TElement[] array = new TElement[maxIdx - minIdx + 1]; diff --git a/src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs index 908ece34831c22..dbcd20974a3066 100644 --- a/src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs @@ -4,107 +4,9 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; namespace System.Linq { - /// - /// Represents an enumerable with zero elements. - /// - /// The element type. - /// - /// Returning an instance of this type is useful to quickly handle scenarios where it is known - /// that an operation will result in zero elements. - /// - [DebuggerDisplay("Count = 0")] - internal sealed class EmptyPartition : IPartition, IEnumerator, IList, IReadOnlyList - { - /// - /// A cached, immutable instance of an empty enumerable. - /// - public static readonly IPartition Instance = new EmptyPartition(); - - private EmptyPartition() - { - } - - public IEnumerator GetEnumerator() => this; - - IEnumerator IEnumerable.GetEnumerator() => this; - - public bool MoveNext() => false; - - [ExcludeFromCodeCoverage(Justification = "Shouldn't be called, and as undefined can return or throw anything anyway")] - public TElement Current => default!; - - [ExcludeFromCodeCoverage(Justification = "Shouldn't be called, and as undefined can return or throw anything anyway")] - object IEnumerator.Current => default!; - - void IEnumerator.Reset() - { - // Do nothing. - } - - void IDisposable.Dispose() - { - // Do nothing. - } - - public IPartition Skip(int count) => this; - - public IPartition Take(int count) => this; - - public TElement? TryGetElementAt(int index, out bool found) - { - found = false; - return default; - } - - public TElement? TryGetFirst(out bool found) - { - found = false; - return default; - } - - public TElement? TryGetLast(out bool found) - { - found = false; - return default; - } - - public TElement[] ToArray() => Array.Empty(); - - public List ToList() => new List(); - - public int GetCount(bool onlyIfCheap) => 0; - - public int Count => 0; - - public bool Contains(TElement item) => false; - - public int IndexOf(TElement item) => -1; - - public void CopyTo(TElement[] array, int arrayIndex) { } - - public TElement this[int index] - { - get - { - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); - return default!; - } - set => ThrowHelper.ThrowNotSupportedException(); - } - - public bool IsReadOnly => true; - - void ICollection.Add(TElement item) => ThrowHelper.ThrowNotSupportedException(); - void ICollection.Clear() => ThrowHelper.ThrowNotSupportedException(); - void IList.Insert(int index, TElement item) => ThrowHelper.ThrowNotSupportedException(); - bool ICollection.Remove(TElement item) => ThrowHelper.ThrowNotSupportedException_Boolean(); - void IList.RemoveAt(int index) => ThrowHelper.ThrowNotSupportedException(); - } - internal sealed class OrderedPartition : IPartition { private readonly OrderedEnumerable _source; @@ -122,10 +24,10 @@ public OrderedPartition(OrderedEnumerable source, int minIdxInclusive, IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public IPartition Skip(int count) + public IPartition? Skip(int count) { int minIndex = unchecked(_minIndexInclusive + count); - return unchecked((uint)minIndex > (uint)_maxIndexInclusive) ? EmptyPartition.Instance : new OrderedPartition(_source, minIndex, _maxIndexInclusive); + return unchecked((uint)minIndex > (uint)_maxIndexInclusive) ? null : new OrderedPartition(_source, minIndex, _maxIndexInclusive); } public IPartition Take(int count) @@ -208,10 +110,10 @@ public override bool MoveNext() public override IEnumerable Select(Func selector) => new SelectListPartitionIterator(_source, selector, _minIndexInclusive, _maxIndexInclusive); - public IPartition Skip(int count) + public IPartition? Skip(int count) { int minIndex = _minIndexInclusive + count; - return (uint)minIndex > (uint)_maxIndexInclusive ? EmptyPartition.Instance : new ListPartition(_source, minIndex, _maxIndexInclusive); + return (uint)minIndex > (uint)_maxIndexInclusive ? null : new ListPartition(_source, minIndex, _maxIndexInclusive); } public IPartition Take(int count) @@ -278,7 +180,7 @@ public TSource[] ToArray() int count = Count; if (count == 0) { - return Array.Empty(); + return []; } TSource[] array = new TSource[count]; @@ -484,7 +386,7 @@ public override bool MoveNext() public override IEnumerable Select(Func selector) => new SelectIPartitionIterator(this, selector); - public IPartition Skip(int count) + public IPartition? Skip(int count) { int minIndex = unchecked(_minIndexInclusive + count); @@ -503,7 +405,7 @@ public IPartition Skip(int count) // If minIndex overflows and we have an upper bound, we will go down this branch. // We know our upper bound must be smaller than minIndex, since our upper bound fits in an int. // This branch should not be taken if we don't have a bound. - return EmptyPartition.Instance; + return null; } Debug.Assert(minIndex >= 0, $"We should have taken care of all cases when {nameof(minIndex)} overflows."); @@ -623,7 +525,7 @@ public TSource[] ToArray() } } - return Array.Empty(); + return []; } public List ToList() diff --git a/src/libraries/System.Linq/src/System/Linq/Range.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Range.SpeedOpt.cs index 176d0edc1a076c..5337f0111e9d93 100644 --- a/src/libraries/System.Linq/src/System/Linq/Range.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Range.SpeedOpt.cs @@ -72,11 +72,11 @@ private static void Fill(Span destination, int value) public int Count => _end - _start; - public IPartition Skip(int count) + public IPartition? Skip(int count) { if (count >= _end - _start) { - return EmptyPartition.Instance; + return null; } return new RangeIterator(_start + count, _end - _start - count); diff --git a/src/libraries/System.Linq/src/System/Linq/Range.cs b/src/libraries/System.Linq/src/System/Linq/Range.cs index b4c0db5277722f..a01c2ff0f3625f 100644 --- a/src/libraries/System.Linq/src/System/Linq/Range.cs +++ b/src/libraries/System.Linq/src/System/Linq/Range.cs @@ -18,7 +18,7 @@ public static IEnumerable Range(int start, int count) if (count == 0) { - return Empty(); + return []; } return new RangeIterator(start, count); diff --git a/src/libraries/System.Linq/src/System/Linq/Repeat.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Repeat.SpeedOpt.cs index 745a11a68608cf..3c25ee20ba5f2c 100644 --- a/src/libraries/System.Linq/src/System/Linq/Repeat.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Repeat.SpeedOpt.cs @@ -36,13 +36,13 @@ public List ToList() public int Count => _count; - public IPartition Skip(int count) + public IPartition? Skip(int count) { Debug.Assert(count > 0); if (count >= _count) { - return EmptyPartition.Instance; + return null; } return new RepeatIterator(_current, _count - count); diff --git a/src/libraries/System.Linq/src/System/Linq/Repeat.cs b/src/libraries/System.Linq/src/System/Linq/Repeat.cs index 10d3d6bd3fe60e..d0ef9941e6ef09 100644 --- a/src/libraries/System.Linq/src/System/Linq/Repeat.cs +++ b/src/libraries/System.Linq/src/System/Linq/Repeat.cs @@ -17,7 +17,7 @@ public static IEnumerable Repeat(TResult element, int count) if (count == 0) { - return Empty(); + return []; } return new RepeatIterator(element, count); diff --git a/src/libraries/System.Linq/src/System/Linq/Reverse.cs b/src/libraries/System.Linq/src/System/Linq/Reverse.cs index c369e6b70f37c9..674b30bbc49c94 100644 --- a/src/libraries/System.Linq/src/System/Linq/Reverse.cs +++ b/src/libraries/System.Linq/src/System/Linq/Reverse.cs @@ -15,6 +15,11 @@ public static IEnumerable Reverse(this IEnumerable so ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } + if (IsEmptyArray(source)) + { + return []; + } + return new ReverseIterator(source); } diff --git a/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs index faf09335d5f3a3..c9e8be3f9744f4 100644 --- a/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs @@ -117,12 +117,12 @@ public int GetCount(bool onlyIfCheap) return _source.Length; } - public IPartition Skip(int count) + public IPartition? Skip(int count) { Debug.Assert(count > 0); if (count >= _source.Length) { - return EmptyPartition.Instance; + return null; } return new SelectListPartitionIterator(_source, _selector, count, int.MaxValue); @@ -132,7 +132,7 @@ public IPartition Take(int count) { Debug.Assert(count > 0); return count >= _source.Length ? - (IPartition)this : + this : new SelectListPartitionIterator(_source, _selector, 0, count - 1); } @@ -241,13 +241,13 @@ public int GetCount(bool onlyIfCheap) return _end - _start; } - public IPartition Skip(int count) + public IPartition? Skip(int count) { Debug.Assert(count > 0); if (count >= (_end - _start)) { - return EmptyPartition.Instance; + return null; } return new SelectRangeIterator(_start + count, _end, _selector); @@ -299,7 +299,7 @@ public TResult[] ToArray() ReadOnlySpan source = CollectionsMarshal.AsSpan(_source); if (source.Length == 0) { - return Array.Empty(); + return []; } var results = new TResult[source.Length]; @@ -401,7 +401,7 @@ public TResult[] ToArray() int count = _source.Count; if (count == 0) { - return Array.Empty(); + return []; } var results = new TResult[count]; @@ -556,16 +556,18 @@ public override void Dispose() public override IEnumerable Select(Func selector) => new SelectIPartitionIterator(_source, CombineSelectors(_selector, selector)); - public IPartition Skip(int count) + public IPartition? Skip(int count) { Debug.Assert(count > 0); - return new SelectIPartitionIterator(_source.Skip(count), _selector); + IPartition? source = _source.Skip(count); + return source is null ? null : new SelectIPartitionIterator(source, _selector); } - public IPartition Take(int count) + public IPartition? Take(int count) { Debug.Assert(count > 0); - return new SelectIPartitionIterator(_source.Take(count), _selector); + IPartition? source = _source.Take(count); + return source is null ? null : new SelectIPartitionIterator(source, _selector); } public TResult? TryGetElementAt(int index, out bool found) @@ -621,7 +623,7 @@ public TResult[] ToArray() return count switch { -1 => LazyToArray(), - 0 => Array.Empty(), + 0 => [], _ => PreallocatingToArray(count), }; } @@ -733,11 +735,11 @@ public override bool MoveNext() public override IEnumerable Select(Func selector) => new SelectListPartitionIterator(_source, CombineSelectors(_selector, selector), _minIndexInclusive, _maxIndexInclusive); - public IPartition Skip(int count) + public IPartition? Skip(int count) { Debug.Assert(count > 0); int minIndex = _minIndexInclusive + count; - return (uint)minIndex > (uint)_maxIndexInclusive ? EmptyPartition.Instance : new SelectListPartitionIterator(_source, _selector, minIndex, _maxIndexInclusive); + return (uint)minIndex > (uint)_maxIndexInclusive ? null : new SelectListPartitionIterator(_source, _selector, minIndex, _maxIndexInclusive); } public IPartition Take(int count) @@ -803,7 +805,7 @@ public TResult[] ToArray() int count = Count; if (count == 0) { - return Array.Empty(); + return []; } TResult[] array = new TResult[count]; diff --git a/src/libraries/System.Linq/src/System/Linq/Select.cs b/src/libraries/System.Linq/src/System/Linq/Select.cs index 403016ee8cce8e..3059fa7f0a8e02 100644 --- a/src/libraries/System.Linq/src/System/Linq/Select.cs +++ b/src/libraries/System.Linq/src/System/Linq/Select.cs @@ -28,18 +28,16 @@ public static IEnumerable Select( return iterator.Select(selector); } - if (GetEmptyIfEmpty(source) is IEnumerable empty) - { - return empty; - } - if (source is IList ilist) { if (source is TSource[] array) { - return array.Length == 0 ? - Empty() : - new SelectArrayIterator(array, selector); + if (array.Length == 0) + { + return []; + } + + return new SelectArrayIterator(array, selector); } if (source is List list) @@ -80,6 +78,11 @@ public static IEnumerable Select(this IEnumerable SelectMany(this IEnumerable ThrowHelper.ThrowArgumentNullException(ExceptionArgument.selector); } + if (IsEmptyArray(source)) + { + return []; + } + return new SelectManySingleSelectorIterator(source, selector); } @@ -35,6 +40,11 @@ public static IEnumerable SelectMany(this IEnumerable ThrowHelper.ThrowArgumentNullException(ExceptionArgument.selector); } + if (IsEmptyArray(source)) + { + return []; + } + return SelectManyIterator(source, selector); } @@ -72,6 +82,11 @@ public static IEnumerable SelectMany(thi ThrowHelper.ThrowArgumentNullException(ExceptionArgument.resultSelector); } + if (IsEmptyArray(source)) + { + return []; + } + return SelectManyIterator(source, collectionSelector, resultSelector); } @@ -109,6 +124,11 @@ public static IEnumerable SelectMany(thi ThrowHelper.ThrowArgumentNullException(ExceptionArgument.resultSelector); } + if (IsEmptyArray(source)) + { + return []; + } + return SelectManyIterator(source, collectionSelector, resultSelector); } diff --git a/src/libraries/System.Linq/src/System/Linq/Skip.cs b/src/libraries/System.Linq/src/System/Linq/Skip.cs index b6cf40526d1d0e..3652d1da3e79e8 100644 --- a/src/libraries/System.Linq/src/System/Linq/Skip.cs +++ b/src/libraries/System.Linq/src/System/Linq/Skip.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Diagnostics; namespace System.Linq { @@ -15,6 +14,11 @@ public static IEnumerable Skip(this IEnumerable sourc ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } + if (IsEmptyArray(source)) + { + return []; + } + if (count <= 0) { // Return source if not actually skipping, but only if it's a type from here, to avoid @@ -28,7 +32,7 @@ public static IEnumerable Skip(this IEnumerable sourc } else if (source is IPartition partition) { - return partition.Skip(count); + return partition.Skip(count) ?? Empty(); } return SkipIterator(source, count); @@ -46,6 +50,11 @@ public static IEnumerable SkipWhile(this IEnumerable ThrowHelper.ThrowArgumentNullException(ExceptionArgument.predicate); } + if (IsEmptyArray(source)) + { + return []; + } + return SkipWhileIterator(source, predicate); } @@ -82,6 +91,11 @@ public static IEnumerable SkipWhile(this IEnumerable ThrowHelper.ThrowArgumentNullException(ExceptionArgument.predicate); } + if (IsEmptyArray(source)) + { + return []; + } + return SkipWhileIterator(source, predicate); } @@ -119,8 +133,9 @@ public static IEnumerable SkipLast(this IEnumerable s ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } - return count <= 0 ? - source.Skip(0) : + return + IsEmptyArray(source) ? [] : + count <= 0 ? source.Skip(0) : TakeRangeFromEndIterator(source, isStartIndexFromEnd: false, startIndex: 0, isEndIndexFromEnd: true, endIndex: count); diff --git a/src/libraries/System.Linq/src/System/Linq/Take.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Take.SpeedOpt.cs index 0242ad1168088e..f97c5295f75a78 100644 --- a/src/libraries/System.Linq/src/System/Linq/Take.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Take.SpeedOpt.cs @@ -10,18 +10,18 @@ public static partial class Enumerable { private static IEnumerable TakeIterator(IEnumerable source, int count) { - Debug.Assert(source != null); + Debug.Assert(source != null && !IsEmptyArray(source)); Debug.Assert(count > 0); return - source is IPartition partition ? partition.Take(count) : + source is IPartition partition ? (partition.Take(count) ?? Empty()) : source is IList sourceList ? new ListPartition(sourceList, 0, count - 1) : new EnumerablePartition(source, 0, count - 1); } private static IEnumerable TakeRangeIterator(IEnumerable source, int startIndex, int endIndex) { - Debug.Assert(source != null); + Debug.Assert(source != null && !IsEmptyArray(source)); Debug.Assert(startIndex >= 0 && startIndex < endIndex); return @@ -29,10 +29,17 @@ private static IEnumerable TakeRangeIterator(IEnumerable sourceList ? new ListPartition(sourceList, startIndex, endIndex - 1) : new EnumerablePartition(source, startIndex, endIndex - 1); - static IPartition TakePartitionRange(IPartition partition, int startIndex, int endIndex) + static IEnumerable TakePartitionRange(IPartition partition, int startIndex, int endIndex) { - partition = endIndex == 0 ? EmptyPartition.Instance : partition.Take(endIndex); - return startIndex == 0 ? partition : partition.Skip(startIndex); + IPartition? source; + if (endIndex != 0 && + (source = partition.Take(endIndex)) is not null && + (startIndex == 0 || (source = source!.Skip(startIndex)) is not null)) + { + return source; + } + + return []; } } } diff --git a/src/libraries/System.Linq/src/System/Linq/Take.cs b/src/libraries/System.Linq/src/System/Linq/Take.cs index 649ce99faeefab..627d5bf03ed78f 100644 --- a/src/libraries/System.Linq/src/System/Linq/Take.cs +++ b/src/libraries/System.Linq/src/System/Linq/Take.cs @@ -15,9 +15,9 @@ public static IEnumerable Take(this IEnumerable sourc ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } - return count <= 0 ? - Empty() : - TakeIterator(source, count); + return count <= 0 || IsEmptyArray(source) ? + [] : + TakeIterator(source, count); } /// Returns a specified range of contiguous elements from a sequence. @@ -37,6 +37,11 @@ public static IEnumerable Take(this IEnumerable sourc ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } + if (IsEmptyArray(source)) + { + return []; + } + Index start = range.Start; Index end = range.End; bool isStartIndexFromEnd = start.IsFromEnd; @@ -50,14 +55,14 @@ public static IEnumerable Take(this IEnumerable sourc { if (startIndex == 0 || (isEndIndexFromEnd && endIndex >= startIndex)) { - return Empty(); + return []; } } else if (!isEndIndexFromEnd) { - return startIndex >= endIndex - ? Empty() - : TakeRangeIterator(source, startIndex, endIndex); + return startIndex >= endIndex ? + [] : + TakeRangeIterator(source, startIndex, endIndex); } return TakeRangeFromEndIterator(source, isStartIndexFromEnd, startIndex, isEndIndexFromEnd, endIndex); @@ -194,6 +199,11 @@ public static IEnumerable TakeWhile(this IEnumerable ThrowHelper.ThrowArgumentNullException(ExceptionArgument.predicate); } + if (IsEmptyArray(source)) + { + return []; + } + return TakeWhileIterator(source, predicate); } @@ -222,6 +232,11 @@ public static IEnumerable TakeWhile(this IEnumerable ThrowHelper.ThrowArgumentNullException(ExceptionArgument.predicate); } + if (IsEmptyArray(source)) + { + return []; + } + return TakeWhileIterator(source, predicate); } @@ -251,8 +266,8 @@ public static IEnumerable TakeLast(this IEnumerable s ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } - return count <= 0 ? - Empty() : + return count <= 0 || IsEmptyArray(source) ? + [] : TakeRangeFromEndIterator(source, isStartIndexFromEnd: true, startIndex: count, isEndIndexFromEnd: true, endIndex: 0); diff --git a/src/libraries/System.Linq/src/System/Linq/ThrowHelper.cs b/src/libraries/System.Linq/src/System/Linq/ThrowHelper.cs index 1bc90d875c94af..28e5cbdf85f3d8 100644 --- a/src/libraries/System.Linq/src/System/Linq/ThrowHelper.cs +++ b/src/libraries/System.Linq/src/System/Linq/ThrowHelper.cs @@ -59,6 +59,7 @@ private static string GetArgumentString(ExceptionArgument argument) case ExceptionArgument.source: return nameof(ExceptionArgument.source); case ExceptionArgument.third: return nameof(ExceptionArgument.third); case ExceptionArgument.size: return nameof(ExceptionArgument.size); + case ExceptionArgument.other: return nameof(ExceptionArgument.other); default: Debug.Fail("The ExceptionArgument value is not defined."); return string.Empty; @@ -87,6 +88,7 @@ internal enum ExceptionArgument selector, source, third, - size + size, + other, } } diff --git a/src/libraries/System.Linq/src/System/Linq/Where.cs b/src/libraries/System.Linq/src/System/Linq/Where.cs index 6a19e5cb471dae..a64233403d813e 100644 --- a/src/libraries/System.Linq/src/System/Linq/Where.cs +++ b/src/libraries/System.Linq/src/System/Linq/Where.cs @@ -28,9 +28,12 @@ public static IEnumerable Where(this IEnumerable sour if (source is TSource[] array) { - return array.Length == 0 ? - Empty() : - new WhereArrayIterator(array, predicate); + if (array.Length == 0) + { + return []; + } + + return new WhereArrayIterator(array, predicate); } if (source is List list) @@ -53,6 +56,11 @@ public static IEnumerable Where(this IEnumerable sour ThrowHelper.ThrowArgumentNullException(ExceptionArgument.predicate); } + if (IsEmptyArray(source)) + { + return []; + } + return WhereIterator(source, predicate); } diff --git a/src/libraries/System.Linq/tests/EmptyEnumerable.cs b/src/libraries/System.Linq/tests/EmptyEnumerable.cs index 86914a7cda19df..6a189bfce4fb6a 100644 --- a/src/libraries/System.Linq/tests/EmptyEnumerable.cs +++ b/src/libraries/System.Linq/tests/EmptyEnumerable.cs @@ -41,7 +41,7 @@ public void EmptyEnumerableIsIndeedEmpty() TestEmptyEmpty(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized))] + [Fact] public void IListImplementationIsValid() { IList list = Assert.IsAssignableFrom>(Enumerable.Empty()); @@ -52,7 +52,7 @@ public void IListImplementationIsValid() Assert.Throws(() => list.Clear()); Assert.Throws(() => list.Remove(42)); Assert.Throws(() => list.RemoveAt(0)); - Assert.Throws(() => list[0] = 42); + AssertExtensions.Throws("index", () => list[0] = 42); AssertExtensions.Throws("index", () => list[0]); AssertExtensions.Throws("index", () => roList[0]); @@ -64,8 +64,8 @@ public void IListImplementationIsValid() Assert.Equal(-1, list.IndexOf(42)); list.CopyTo(Array.Empty(), 0); - list.CopyTo(Array.Empty(), 1); - int[] array = new int[1] { 42 }; + AssertExtensions.Throws("destinationArray", () => list.CopyTo(Array.Empty(), 1)); + int[] array = [42]; list.CopyTo(array, 0); Assert.Equal(42, array[0]); } diff --git a/src/libraries/System.Linq/tests/EmptyPartitionTests.cs b/src/libraries/System.Linq/tests/EmptyPartitionTests.cs deleted file mode 100644 index 82ae516a713088..00000000000000 --- a/src/libraries/System.Linq/tests/EmptyPartitionTests.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using Xunit; - -namespace System.Linq.Tests -{ - public class EmptyPartitionTests - { - private static IEnumerable GetEmptyPartition() - { - return new T[0].Take(0); - } - - [Fact] - public void EmptyPartitionIsEmpty() - { - Assert.Empty(GetEmptyPartition()); - Assert.Empty(GetEmptyPartition()); - } - - [Fact] - public void SingleInstance() - { - // .NET Core returns the instance as an optimization. - // see https://github.com/dotnet/corefx/pull/2401. - Assert.True(ReferenceEquals(GetEmptyPartition(), GetEmptyPartition())); - } - - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized))] - public void SkipSame() - { - IEnumerable empty = GetEmptyPartition(); - Assert.Same(empty, empty.Skip(2)); - } - - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized))] - public void TakeSame() - { - IEnumerable empty = GetEmptyPartition(); - Assert.Same(empty, empty.Take(2)); - } - - [Fact] - public void ElementAtThrows() - { - AssertExtensions.Throws("index", () => GetEmptyPartition().ElementAt(0)); - } - - [Fact] - public void ElementAtOrDefaultIsDefault() - { - Assert.Equal(0, GetEmptyPartition().ElementAtOrDefault(0)); - Assert.Null(GetEmptyPartition().ElementAtOrDefault(0)); - } - - [Fact] - public void FirstThrows() - { - Assert.Throws(() => GetEmptyPartition().First()); - } - - [Fact] - public void FirstOrDefaultIsDefault() - { - Assert.Equal(0, GetEmptyPartition().FirstOrDefault()); - Assert.Null(GetEmptyPartition().FirstOrDefault()); - } - - [Fact] - public void LastThrows() - { - Assert.Throws(() => GetEmptyPartition().Last()); - } - - [Fact] - public void LastOrDefaultIsDefault() - { - Assert.Equal(0, GetEmptyPartition().LastOrDefault()); - Assert.Null(GetEmptyPartition().LastOrDefault()); - } - - [Fact] - public void ToArrayEmpty() - { - Assert.Empty(GetEmptyPartition().ToArray()); - } - - [Fact] - public void ToListEmpty() - { - Assert.Empty(GetEmptyPartition().ToList()); - } - - [Fact] - public void ResetIsNop() - { - IEnumerator en = GetEmptyPartition().GetEnumerator(); - en.Reset(); - en.Reset(); - en.Reset(); - } - - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized))] - public void IListImplementationIsValid() - { - IList list = Assert.IsAssignableFrom>(Enumerable.Empty()); - IReadOnlyList roList = Assert.IsAssignableFrom>(Enumerable.Empty()); - - Assert.Throws(() => list.Add(42)); - Assert.Throws(() => list.Insert(0, 42)); - Assert.Throws(() => list.Clear()); - Assert.Throws(() => list.Remove(42)); - Assert.Throws(() => list.RemoveAt(0)); - Assert.Throws(() => list[0] = 42); - AssertExtensions.Throws("index", () => list[0]); - AssertExtensions.Throws("index", () => roList[0]); - - Assert.True(list.IsReadOnly); - Assert.Equal(0, list.Count); - Assert.Equal(0, roList.Count); - - Assert.False(list.Contains(42)); - Assert.Equal(-1, list.IndexOf(42)); - - list.CopyTo(Array.Empty(), 0); - list.CopyTo(Array.Empty(), 1); - int[] array = new int[1] { 42 }; - list.CopyTo(array, 0); - Assert.Equal(42, array[0]); - } - } -} diff --git a/src/libraries/System.Linq/tests/System.Linq.Tests.csproj b/src/libraries/System.Linq/tests/System.Linq.Tests.csproj index cff42c6c826c52..de5d8b642a3d14 100644 --- a/src/libraries/System.Linq/tests/System.Linq.Tests.csproj +++ b/src/libraries/System.Linq/tests/System.Linq.Tests.csproj @@ -26,7 +26,6 @@ - From 0823c5c2814ffad5718f06715899ef91aa84aae2 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 8 Jan 2024 11:34:13 -0500 Subject: [PATCH 05/20] Update Enumerable.Min/Max for all IBinaryIntegers (#96605) The implementations are special-casing most of the built-in ones, in order to delegate to the IBinaryInteger-constrained helper, but it was missing Int128, UInt128, and char. ?hese won't be vectorized, but they'll at least use the more streamlined non-vectorized implementations. --- src/libraries/System.Linq/src/System/Linq/Max.cs | 4 ++++ src/libraries/System.Linq/src/System/Linq/MaxMin.cs | 6 +++--- src/libraries/System.Linq/src/System/Linq/Min.cs | 4 ++++ src/libraries/System.Linq/tests/MaxTests.cs | 9 +++++++++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/Max.cs b/src/libraries/System.Linq/src/System/Linq/Max.cs index bd08684270f6c7..d3189720456d73 100644 --- a/src/libraries/System.Linq/src/System/Linq/Max.cs +++ b/src/libraries/System.Linq/src/System/Linq/Max.cs @@ -319,16 +319,20 @@ public static decimal Max(this IEnumerable source) comparer ??= Comparer.Default; + // TODO https://github.com/dotnet/csharplang/discussions/6308: Update this to use generic constraint bridging if/when available. if (typeof(TSource) == typeof(byte) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(sbyte) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(ushort) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(short) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); + if (typeof(TSource) == typeof(char) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(uint) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(int) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(ulong) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(long) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(nuint) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(nint) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); + if (typeof(TSource) == typeof(Int128) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); + if (typeof(TSource) == typeof(UInt128) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); TSource? value = default; using (IEnumerator e = source.GetEnumerator()) diff --git a/src/libraries/System.Linq/src/System/Linq/MaxMin.cs b/src/libraries/System.Linq/src/System/Linq/MaxMin.cs index 47974c48a1c6fc..759c4ce65627dd 100644 --- a/src/libraries/System.Linq/src/System/Linq/MaxMin.cs +++ b/src/libraries/System.Linq/src/System/Linq/MaxMin.cs @@ -32,7 +32,7 @@ private static T MinMaxInteger(this IEnumerable source) ThrowHelper.ThrowNoElementsException(); } - if (!Vector128.IsHardwareAccelerated || span.Length < Vector128.Count) + if (!Vector128.IsHardwareAccelerated || !Vector128.IsSupported || span.Length < Vector128.Count) { value = span[0]; for (int i = 1; i < span.Length; i++) @@ -43,7 +43,7 @@ private static T MinMaxInteger(this IEnumerable source) } } } - else if (!Vector256.IsHardwareAccelerated || span.Length < Vector256.Count) + else if (!Vector256.IsHardwareAccelerated || !Vector256.IsSupported || span.Length < Vector256.Count) { ref T current = ref MemoryMarshal.GetReference(span); ref T lastVectorStart = ref Unsafe.Add(ref current, span.Length - Vector128.Count); @@ -67,7 +67,7 @@ private static T MinMaxInteger(this IEnumerable source) } } } - else if (!Vector512.IsHardwareAccelerated || span.Length < Vector512.Count) + else if (!Vector512.IsHardwareAccelerated || !Vector512.IsSupported || span.Length < Vector512.Count) { ref T current = ref MemoryMarshal.GetReference(span); ref T lastVectorStart = ref Unsafe.Add(ref current, span.Length - Vector256.Count); diff --git a/src/libraries/System.Linq/src/System/Linq/Min.cs b/src/libraries/System.Linq/src/System/Linq/Min.cs index 6c81274f3013c8..632d643655174a 100644 --- a/src/libraries/System.Linq/src/System/Linq/Min.cs +++ b/src/libraries/System.Linq/src/System/Linq/Min.cs @@ -297,16 +297,20 @@ public static decimal Min(this IEnumerable source) comparer ??= Comparer.Default; + // TODO https://github.com/dotnet/csharplang/discussions/6308: Update this to use generic constraint bridging if/when available. if (typeof(TSource) == typeof(byte) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(sbyte) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(ushort) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(short) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); + if (typeof(TSource) == typeof(char) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(uint) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(int) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(ulong) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(long) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(nuint) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); if (typeof(TSource) == typeof(nint) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); + if (typeof(TSource) == typeof(Int128) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); + if (typeof(TSource) == typeof(UInt128) && comparer == Comparer.Default) return (TSource)(object)MinMaxInteger>((IEnumerable)source); TSource? value = default; using (IEnumerator e = source.GetEnumerator()) diff --git a/src/libraries/System.Linq/tests/MaxTests.cs b/src/libraries/System.Linq/tests/MaxTests.cs index d9b020e7d47964..a1509855091d11 100644 --- a/src/libraries/System.Linq/tests/MaxTests.cs +++ b/src/libraries/System.Linq/tests/MaxTests.cs @@ -28,6 +28,9 @@ public static IEnumerable Max_AllTypes_TestData() yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).Select(i => (short)i)), (short)(length + length - 1) }; yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).Select(i => (short)i).ToArray()), (short)(length + length - 1) }; + yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).Select(i => (char)i)), (char)(length + length - 1) }; + yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).Select(i => (char)i).ToArray()), (char)(length + length - 1) }; + yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).Select(i => (uint)i)), (uint)(length + length - 1) }; yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).Select(i => (uint)i).ToArray()), (uint)(length + length - 1) }; @@ -54,6 +57,12 @@ public static IEnumerable Max_AllTypes_TestData() yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).Select(i => (nint)i)), (nint)(length + length - 1) }; yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).Select(i => (nint)i).ToArray()), (nint)(length + length - 1) }; + + yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).Select(i => (Int128)i)), (Int128)(length + length - 1) }; + yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).Select(i => (Int128)i).ToArray()), (Int128)(length + length - 1) }; + + yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).Select(i => (UInt128)i)), (UInt128)(length + length - 1) }; + yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).Select(i => (UInt128)i).ToArray()), (UInt128)(length + length - 1) }; } } From 37ed0eee99e0416c02eb1a505411b874ed0fc58b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Andr=C3=A9?= <2341261+manandre@users.noreply.github.com> Date: Mon, 8 Jan 2024 18:33:14 +0100 Subject: [PATCH 06/20] Allow specifying IndentCharacter and IndentSize when writing JSON (#95292) * Add IndentText json option * Add IndentText for json source generator * Add tests * IndentText must be non-nullable * Improve performance * Add extra tests * Cleanup * Apply suggestions from code review Co-authored-by: Eirik Tsarpalis * Fixes following code review * Fixes following code review #2 * Add tests for invalid characters * Handle RawIndent length * Move all to RawIndentation * Update documentation * Additional fixes from code review * Move to the new API * Extra fixes and enhancements * Fixes from code review * Avoid introducing extra fields in JsonWriterOptions * Fix OOM error * Use bitwise logic for IndentedOrNotSkipValidation * Cache indentation options in Utf8JsonWriter * Add missing test around indentation options * New fixes from code review * Update src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs * Add test to check default values of the JsonWriterOptions properties * Fix comment --------- Co-authored-by: Eirik Tsarpalis --- .../ConsoleLoggerConfigureOptions.cs | 2 +- .../ConsoleLoggerExtensionsTests.cs | 1 + .../JsonSourceGenerationOptionsAttribute.cs | 10 + .../gen/JsonSourceGenerator.Emitter.cs | 7 + .../gen/JsonSourceGenerator.Parser.cs | 12 + .../gen/Model/SourceGenerationOptionsSpec.cs | 4 + .../System.Text.Json/ref/System.Text.Json.cs | 6 + .../src/Resources/Strings.resx | 6 + .../src/System/Text/Json/JsonConstants.cs | 9 +- .../JsonSerializerOptions.Caching.cs | 4 + .../Serialization/JsonSerializerOptions.cs | 50 + .../src/System/Text/Json/ThrowHelper.cs | 12 + .../Text/Json/Writer/JsonWriterHelper.cs | 30 +- .../Text/Json/Writer/JsonWriterOptions.cs | 46 +- .../Utf8JsonWriter.WriteProperties.Bytes.cs | 8 +- ...Utf8JsonWriter.WriteProperties.DateTime.cs | 8 +- ...onWriter.WriteProperties.DateTimeOffset.cs | 8 +- .../Utf8JsonWriter.WriteProperties.Decimal.cs | 8 +- .../Utf8JsonWriter.WriteProperties.Double.cs | 8 +- .../Utf8JsonWriter.WriteProperties.Float.cs | 8 +- .../Utf8JsonWriter.WriteProperties.Guid.cs | 8 +- .../Utf8JsonWriter.WriteProperties.Helpers.cs | 8 +- .../Utf8JsonWriter.WriteProperties.Literal.cs | 8 +- ...JsonWriter.WriteProperties.SignedNumber.cs | 8 +- .../Utf8JsonWriter.WriteProperties.String.cs | 24 +- ...onWriter.WriteProperties.UnsignedNumber.cs | 8 +- .../Utf8JsonWriter.WriteValues.Bytes.cs | 4 +- .../Utf8JsonWriter.WriteValues.Comment.cs | 8 +- .../Utf8JsonWriter.WriteValues.DateTime.cs | 4 +- ...f8JsonWriter.WriteValues.DateTimeOffset.cs | 4 +- .../Utf8JsonWriter.WriteValues.Decimal.cs | 4 +- .../Utf8JsonWriter.WriteValues.Double.cs | 4 +- .../Utf8JsonWriter.WriteValues.Float.cs | 4 +- ...8JsonWriter.WriteValues.FormattedNumber.cs | 4 +- .../Writer/Utf8JsonWriter.WriteValues.Guid.cs | 4 +- .../Utf8JsonWriter.WriteValues.Literal.cs | 4 +- ...Utf8JsonWriter.WriteValues.SignedNumber.cs | 4 +- .../Utf8JsonWriter.WriteValues.String.cs | 8 +- ...f8JsonWriter.WriteValues.UnsignedNumber.cs | 4 +- .../System/Text/Json/Writer/Utf8JsonWriter.cs | 45 +- .../JsonSourceGenerationOptionsTests.cs | 6 +- .../JsonWriterOptionsTests.cs | 103 +- .../Serialization/CacheTests.cs | 2 + .../Serialization/OptionsTests.cs | 88 + .../Utf8JsonWriterTests.cs | 1603 ++++++----------- 45 files changed, 1087 insertions(+), 1131 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerConfigureOptions.cs b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerConfigureOptions.cs index c30e842d3fc3ee..3b725f1cb59e03 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerConfigureOptions.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerConfigureOptions.cs @@ -25,7 +25,7 @@ public void EnsureConsoleLoggerOptions_ConfigureOptions_SupportsAllProperties() Assert.Equal(3, typeof(ConsoleFormatterOptions).GetProperties(flags).Length); Assert.Equal(5, typeof(SimpleConsoleFormatterOptions).GetProperties(flags).Length); Assert.Equal(4, typeof(JsonConsoleFormatterOptions).GetProperties(flags).Length); - Assert.Equal(4, typeof(JsonWriterOptions).GetProperties(flags).Length); + Assert.Equal(6, typeof(JsonWriterOptions).GetProperties(flags).Length); } [Theory] diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs index 99b49170ed8e1d..fec2d737c8d2c8 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs @@ -597,6 +597,7 @@ private static void VerifyHasOnlySimpleProperties(Type type) // or else NativeAOT would break Assert.True(prop.PropertyType == typeof(string) || prop.PropertyType == typeof(bool) || + prop.PropertyType == typeof(char) || prop.PropertyType == typeof(int) || prop.PropertyType.IsEnum, $"ConsoleOptions property '{type.Name}.{prop.Name}' must be a simple type in order for NativeAOT to work"); } diff --git a/src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs b/src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs index 8084d4a6f613f2..0bfaf896c40343 100644 --- a/src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs +++ b/src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs @@ -125,6 +125,16 @@ public JsonSourceGenerationOptionsAttribute(JsonSerializerDefaults defaults) /// public bool WriteIndented { get; set; } + /// + /// Specifies the default value of when set. + /// + public char IndentCharacter { get; set; } + + /// + /// Specifies the default value of when set. + /// + public int IndentSize { get; set; } + /// /// Specifies the default source generation mode for type declarations that don't set a . /// diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index db08be80c53d02..a7e396983d15ab 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -1168,6 +1168,12 @@ private static void GetLogicForDefaultSerializerOptionsInit(SourceGenerationOpti if (optionsSpec.WriteIndented is bool writeIndented) writer.WriteLine($"WriteIndented = {FormatBool(writeIndented)},"); + if (optionsSpec.IndentCharacter is char indentCharacter) + writer.WriteLine($"IndentCharacter = {FormatIndentChar(indentCharacter)},"); + + if (optionsSpec.IndentSize is int indentSize) + writer.WriteLine($"IndentSize = {indentSize},"); + writer.Indentation--; writer.WriteLine("};"); @@ -1344,6 +1350,7 @@ private static string FormatJsonSerializerDefaults(JsonSerializerDefaults defaul private static string FormatBool(bool value) => value ? "true" : "false"; private static string FormatStringLiteral(string? value) => value is null ? "null" : $"\"{value}\""; + private static string FormatIndentChar(char value) => value is '\t' ? "'\\t'" : $"'{value}'"; /// /// Method used to generate JsonTypeInfo given options instance diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 594f7ad9770c3c..3be80faceb84fe 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -280,6 +280,8 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN JsonUnmappedMemberHandling? unmappedMemberHandling = null; bool? useStringEnumConverter = null; bool? writeIndented = null; + char? indentCharacter = null; + int? indentSize = null; if (attributeData.ConstructorArguments.Length > 0) { @@ -373,6 +375,14 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN writeIndented = (bool)namedArg.Value.Value!; break; + case nameof(JsonSourceGenerationOptionsAttribute.IndentCharacter): + indentCharacter = (char)namedArg.Value.Value!; + break; + + case nameof(JsonSourceGenerationOptionsAttribute.IndentSize): + indentSize = (int)namedArg.Value.Value!; + break; + case nameof(JsonSourceGenerationOptionsAttribute.GenerationMode): generationMode = (JsonSourceGenerationMode)namedArg.Value.Value!; break; @@ -404,6 +414,8 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN UnmappedMemberHandling = unmappedMemberHandling, UseStringEnumConverter = useStringEnumConverter, WriteIndented = writeIndented, + IndentCharacter = indentCharacter, + IndentSize = indentSize, }; } diff --git a/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs b/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs index 83b587fb962f7e..17126929c0e096 100644 --- a/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs +++ b/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs @@ -52,6 +52,10 @@ public sealed record SourceGenerationOptionsSpec public required bool? WriteIndented { get; init; } + public required char? IndentCharacter { get; init; } + + public required int? IndentSize { get; init; } + public JsonKnownNamingPolicy? GetEffectivePropertyNamingPolicy() => PropertyNamingPolicy ?? (Defaults is JsonSerializerDefaults.Web ? JsonKnownNamingPolicy.CamelCase : null); } diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index 6014c8e5dc6300..25ccb474dbbb6a 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -395,6 +395,8 @@ public JsonSerializerOptions(System.Text.Json.JsonSerializerOptions options) { } public System.Text.Json.Serialization.JsonUnknownTypeHandling UnknownTypeHandling { get { throw null; } set { } } public System.Text.Json.Serialization.JsonUnmappedMemberHandling UnmappedMemberHandling { get { throw null; } set { } } public bool WriteIndented { get { throw null; } set { } } + public char IndentCharacter { get { throw null; } set { } } + public int IndentSize { get { throw null; } set { } } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] [System.ObsoleteAttribute("JsonSerializerOptions.AddContext is obsolete. To register a JsonSerializerContext, use either the TypeInfoResolver or TypeInfoResolverChain properties.", DiagnosticId="SYSLIB0049", UrlFormat="https://aka.ms/dotnet-warnings/{0}")] public void AddContext() where TContext : System.Text.Json.Serialization.JsonSerializerContext, new() { } @@ -440,6 +442,8 @@ public partial struct JsonWriterOptions private int _dummyPrimitive; public System.Text.Encodings.Web.JavaScriptEncoder? Encoder { readonly get { throw null; } set { } } public bool Indented { get { throw null; } set { } } + public char IndentCharacter { get { throw null; } set { } } + public int IndentSize { get { throw null; } set { } } public int MaxDepth { readonly get { throw null; } set { } } public bool SkipValidation { get { throw null; } set { } } } @@ -1075,6 +1079,8 @@ public JsonSourceGenerationOptionsAttribute(System.Text.Json.JsonSerializerDefau public System.Text.Json.Serialization.JsonUnmappedMemberHandling UnmappedMemberHandling { get { throw null; } set { } } public bool UseStringEnumConverter { get { throw null; } set { } } public bool WriteIndented { get { throw null; } set { } } + public char IndentCharacter { get { throw null; } set { } } + public int IndentSize { get { throw null; } set { } } } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JsonStringEnumConverter cannot be statically analyzed and requires runtime code generation. Applications should use the generic JsonStringEnumConverter instead.")] public partial class JsonStringEnumConverter : System.Text.Json.Serialization.JsonConverterFactory diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 411d20d45d6be0..acd31cc6cce253 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -708,4 +708,10 @@ Either the JSON value is not in a supported format, or is out of bounds for a Half. + + Supported indentation characters are space and horizontal tab. + + + Indentation size must be between {0} and {1}. + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs index 30751a04a363e7..99012e30e8b36b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs @@ -47,7 +47,6 @@ internal static partial class JsonConstants // Explicitly skipping ReverseSolidus since that is handled separately public static ReadOnlySpan EscapableChars => "\"nrt/ubf"u8; - public const int SpacesPerIndent = 2; public const int RemoveFlagsBitMask = 0x7FFFFFFF; // In the worst case, an ASCII character represented as a single utf-8 byte could expand 6x when escaped. @@ -110,5 +109,13 @@ internal static partial class JsonConstants // The maximum number of parameters a constructor can have where it can be considered // for a path on deserialization where we don't box the constructor arguments. public const int UnboxedParameterCountThreshold = 4; + + // Two space characters is the default indentation. + public const char DefaultIndentCharacter = ' '; + public const char TabIndentCharacter = '\t'; + public const int DefaultIndentSize = 2; + public const int MinimumIndentSize = 0; + public const int MaximumIndentSize = 127; // If this value is changed, the impact on the options masking used in the JsonWriterOptions struct must be checked carefully. + } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs index 68551c7eedf12a..f76bdea2de2a36 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs @@ -511,6 +511,8 @@ public bool Equals(JsonSerializerOptions? left, JsonSerializerOptions? right) left._includeFields == right._includeFields && left._propertyNameCaseInsensitive == right._propertyNameCaseInsensitive && left._writeIndented == right._writeIndented && + left._indentCharacter == right._indentCharacter && + left._indentSize == right._indentSize && left._typeInfoResolver == right._typeInfoResolver && CompareLists(left._converters, right._converters); @@ -565,6 +567,8 @@ public int GetHashCode(JsonSerializerOptions options) AddHashCode(ref hc, options._includeFields); AddHashCode(ref hc, options._propertyNameCaseInsensitive); AddHashCode(ref hc, options._writeIndented); + AddHashCode(ref hc, options._indentCharacter); + AddHashCode(ref hc, options._indentSize); AddHashCode(ref hc, options._typeInfoResolver); AddListHashCode(ref hc, options._converters); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs index 20df47b4666497..78d02af2b78465 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs @@ -90,6 +90,8 @@ public static JsonSerializerOptions Web private bool _includeFields; private bool _propertyNameCaseInsensitive; private bool _writeIndented; + private char _indentCharacter = JsonConstants.DefaultIndentCharacter; + private int _indentSize = JsonConstants.DefaultIndentSize; /// /// Constructs a new instance. @@ -139,6 +141,8 @@ public JsonSerializerOptions(JsonSerializerOptions options) _includeFields = options._includeFields; _propertyNameCaseInsensitive = options._propertyNameCaseInsensitive; _writeIndented = options._writeIndented; + _indentCharacter = options._indentCharacter; + _indentSize = options._indentSize; _typeInfoResolver = options._typeInfoResolver; EffectiveMaxDepth = options.EffectiveMaxDepth; ReferenceHandlingStrategy = options.ReferenceHandlingStrategy; @@ -660,6 +664,50 @@ public bool WriteIndented } } + /// + /// Defines the indentation character being used when is enabled. Defaults to the space character. + /// + /// Allowed characters are space and horizontal tab. + /// contains an invalid character. + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// + public char IndentCharacter + { + get + { + return _indentCharacter; + } + set + { + JsonWriterHelper.ValidateIndentCharacter(value); + VerifyMutable(); + _indentCharacter = value; + } + } + + /// + /// Defines the indentation size being used when is enabled. Defaults to two. + /// + /// Allowed values are all integers between 0 and 127, included. + /// is out of the allowed range. + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// + public int IndentSize + { + get + { + return _indentSize; + } + set + { + JsonWriterHelper.ValidateIndentSize(value); + VerifyMutable(); + _indentSize = value; + } + } + /// /// Configures how object references are handled when reading and writing JSON. /// @@ -891,6 +939,8 @@ internal JsonWriterOptions GetWriterOptions() { Encoder = Encoder, Indented = WriteIndented, + IndentCharacter = IndentCharacter, + IndentSize = IndentSize, MaxDepth = EffectiveMaxDepth, #if !DEBUG SkipValidation = true diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs index 3d8913f8d2c782..58bd0a71724911 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs @@ -12,6 +12,18 @@ internal static partial class ThrowHelper // If the exception source is this value, the serializer will re-throw as JsonException. public const string ExceptionSourceValueToRethrowAsJsonException = "System.Text.Json.Rethrowable"; + [DoesNotReturn] + public static void ThrowArgumentOutOfRangeException_IndentCharacter(string parameterName) + { + throw GetArgumentOutOfRangeException(parameterName, SR.InvalidIndentCharacter); + } + + [DoesNotReturn] + public static void ThrowArgumentOutOfRangeException_IndentSize(string parameterName, int minimumSize, int maximumSize) + { + throw GetArgumentOutOfRangeException(parameterName, SR.Format(SR.InvalidIndentSize, minimumSize, maximumSize)); + } + [DoesNotReturn] public static void ThrowArgumentOutOfRangeException_MaxDepthMustBePositive(string parameterName) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs index 97037ddbff7270..c34c8cd3d672f9 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs @@ -10,9 +10,8 @@ namespace System.Text.Json { internal static partial class JsonWriterHelper { - public static void WriteIndentation(Span buffer, int indent) + public static void WriteIndentation(Span buffer, int indent, byte indentByte) { - Debug.Assert(indent % JsonConstants.SpacesPerIndent == 0); Debug.Assert(buffer.Length >= indent); // Based on perf tests, the break-even point where vectorized Fill is faster @@ -20,18 +19,37 @@ public static void WriteIndentation(Span buffer, int indent) if (indent < 8) { int i = 0; - while (i < indent) + while (i + 1 < indent) { - buffer[i++] = JsonConstants.Space; - buffer[i++] = JsonConstants.Space; + buffer[i++] = indentByte; + buffer[i++] = indentByte; + } + + if (i < indent) + { + buffer[i] = indentByte; } } else { - buffer.Slice(0, indent).Fill(JsonConstants.Space); + buffer.Slice(0, indent).Fill(indentByte); } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidateIndentCharacter(char value) + { + if (value is not JsonConstants.DefaultIndentCharacter and not JsonConstants.TabIndentCharacter) + ThrowHelper.ThrowArgumentOutOfRangeException_IndentCharacter(nameof(value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidateIndentSize(int value) + { + if (value is < JsonConstants.MinimumIndentSize or > JsonConstants.MaximumIndentSize) + ThrowHelper.ThrowArgumentOutOfRangeException_IndentSize(nameof(value), JsonConstants.MinimumIndentSize, JsonConstants.MaximumIndentSize); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ValidateProperty(ReadOnlySpan propertyName) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterOptions.cs index 2d89a261e33535..1d89e5d6aa1499 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterOptions.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterOptions.cs @@ -43,6 +43,48 @@ public bool Indented } } + /// + /// Defines the indentation character used by when is enabled. Defaults to the space character. + /// + /// Allowed characters are space and horizontal tab. + /// contains an invalid character. + public char IndentCharacter + { + readonly get => (_optionsMask & IndentCharacterBit) != 0 ? JsonConstants.TabIndentCharacter : JsonConstants.DefaultIndentCharacter; + set + { + JsonWriterHelper.ValidateIndentCharacter(value); + if (value is not JsonConstants.DefaultIndentCharacter) + _optionsMask |= IndentCharacterBit; + else + _optionsMask &= ~IndentCharacterBit; + } + } + + /// + /// Defines the indentation size used by when is enabled. Defaults to two. + /// + /// Allowed values are integers between 0 and 127, included. + /// is out of the allowed range. + public int IndentSize + { + readonly get => EncodeIndentSize((_optionsMask & IndentSizeMask) >> 3); + set + { + JsonWriterHelper.ValidateIndentSize(value); + _optionsMask = (_optionsMask & ~IndentSizeMask) | (EncodeIndentSize(value) << 3); + } + } + + // Encoding is applied by swapping 0 with the default value to ensure default(JsonWriterOptions) instances are well-defined. + // As this operation is symmetrical, it can also be used to decode. + private static int EncodeIndentSize(int value) => value switch + { + 0 => JsonConstants.DefaultIndentSize, + JsonConstants.DefaultIndentSize => 0, + _ => value + }; + /// /// Gets or sets the maximum depth allowed when writing JSON, with the default (i.e. 0) indicating a max depth of 1000. /// @@ -93,9 +135,11 @@ public bool SkipValidation } } - internal bool IndentedOrNotSkipValidation => _optionsMask != SkipValidationBit; // Equivalent to: Indented || !SkipValidation; + internal bool IndentedOrNotSkipValidation => (_optionsMask & (IndentBit | SkipValidationBit)) != SkipValidationBit; // Equivalent to: Indented || !SkipValidation; private const int IndentBit = 1; private const int SkipValidationBit = 2; + private const int IndentCharacterBit = 4; + private const int IndentSizeMask = JsonConstants.MaximumIndentSize << 3; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.cs index 9c4785e56bfab1..a8d53aef63dec0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.cs @@ -280,7 +280,7 @@ private void WriteBase64Minimized(ReadOnlySpan escapedPropertyName, ReadOn private void WriteBase64Indented(ReadOnlySpan escapedPropertyName, ReadOnlySpan bytes) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); int encodedLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length); @@ -309,7 +309,7 @@ private void WriteBase64Indented(ReadOnlySpan escapedPropertyName, ReadOnl WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; @@ -330,7 +330,7 @@ private void WriteBase64Indented(ReadOnlySpan escapedPropertyName, ReadOnl private void WriteBase64Indented(ReadOnlySpan escapedPropertyName, ReadOnlySpan bytes) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); int encodedLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length); @@ -359,7 +359,7 @@ private void WriteBase64Indented(ReadOnlySpan escapedPropertyName, ReadOnl WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs index 55c079f008faa2..392facd7d85ee2 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs @@ -284,7 +284,7 @@ private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, DateTi private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTime value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength); @@ -311,7 +311,7 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTim WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; @@ -333,7 +333,7 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTim private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTime value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength); @@ -359,7 +359,7 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTim WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs index 9ace783d791e48..313693b3b7665f 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs @@ -283,7 +283,7 @@ private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, DateTi private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTimeOffset value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength); @@ -310,7 +310,7 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTim WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; @@ -332,7 +332,7 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTim private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTimeOffset value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength); @@ -358,7 +358,7 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTim WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs index 933fcffc4fc1b1..3f1af56067cd9b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs @@ -277,7 +277,7 @@ private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, decima private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, decimal value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDecimalLength - 5 - s_newLineLength); @@ -304,7 +304,7 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, decimal WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; @@ -323,7 +323,7 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, decimal private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, decimal value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDecimalLength - 5 - s_newLineLength); @@ -349,7 +349,7 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, decimal WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs index 73bcc75f72edeb..eee0535903cfa1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs @@ -281,7 +281,7 @@ private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, double private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, double value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDoubleLength - 5 - s_newLineLength); @@ -308,7 +308,7 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, double WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; @@ -327,7 +327,7 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, double private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, double value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDoubleLength - 5 - s_newLineLength); @@ -353,7 +353,7 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, double WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs index bb695740d290fa..133c95ece5d865 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs @@ -281,7 +281,7 @@ private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, float private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, float value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatSingleLength - 5 - s_newLineLength); @@ -308,7 +308,7 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, float v WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; @@ -327,7 +327,7 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, float v private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, float value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatSingleLength - 5 - s_newLineLength); @@ -353,7 +353,7 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, float v WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs index a0c5713374ca1b..1ded7b8f3f6a15 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs @@ -285,7 +285,7 @@ private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, Guid v private void WriteStringIndented(ReadOnlySpan escapedPropertyName, Guid value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatGuidLength - 7 - s_newLineLength); @@ -312,7 +312,7 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, Guid va WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; @@ -335,7 +335,7 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, Guid va private void WriteStringIndented(ReadOnlySpan escapedPropertyName, Guid value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatGuidLength - 7 - s_newLineLength); @@ -361,7 +361,7 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, Guid va WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs index cd56c0a99aa9e3..c09aaa40c8a23e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs @@ -89,7 +89,7 @@ private void WritePropertyNameMinimized(ReadOnlySpan escapedPropertyName, private void WritePropertyNameIndented(ReadOnlySpan escapedPropertyName, byte token) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - 6 - s_newLineLength); @@ -115,7 +115,7 @@ private void WritePropertyNameIndented(ReadOnlySpan escapedPropertyName, b WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; @@ -161,7 +161,7 @@ private void WritePropertyNameMinimized(ReadOnlySpan escapedPropertyName, private void WritePropertyNameIndented(ReadOnlySpan escapedPropertyName, byte token) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 6 - s_newLineLength); @@ -188,7 +188,7 @@ private void WritePropertyNameIndented(ReadOnlySpan escapedPropertyName, b WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs index e73131b7192f93..aff0da471b1a3f 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs @@ -427,7 +427,7 @@ private void WriteLiteralSection(ReadOnlySpan escapedPropertyNameSection, private void WriteLiteralIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(value.Length <= JsonConstants.MaxUnescapedTokenSize); Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - value.Length - 5 - s_newLineLength); @@ -455,7 +455,7 @@ private void WriteLiteralIndented(ReadOnlySpan escapedPropertyName, ReadOn WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; @@ -473,7 +473,7 @@ private void WriteLiteralIndented(ReadOnlySpan escapedPropertyName, ReadOn private void WriteLiteralIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(value.Length <= JsonConstants.MaxUnescapedTokenSize); Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - value.Length - 5 - s_newLineLength); @@ -500,7 +500,7 @@ private void WriteLiteralIndented(ReadOnlySpan escapedPropertyName, ReadOn WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs index 10c390a98ffc43..82694f738cc76c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs @@ -353,7 +353,7 @@ private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, long v private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, long value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatInt64Length - 5 - s_newLineLength); @@ -380,7 +380,7 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, long va WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; @@ -399,7 +399,7 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, long va private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, long value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatInt64Length - 5 - s_newLineLength); @@ -425,7 +425,7 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, long va WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs index 7db29516ddbe0f..16629ea424f4cf 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs @@ -187,7 +187,7 @@ private void WriteStringMinimizedPropertyName(ReadOnlySpan escapedProperty private void WriteStringIndentedPropertyName(ReadOnlySpan escapedPropertyName) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length <= JsonConstants.MaxEscapedTokenSize); Debug.Assert(escapedPropertyName.Length < (int.MaxValue - 5 - indent - s_newLineLength) / JsonConstants.MaxExpansionFactorWhileTranscoding); @@ -213,7 +213,7 @@ private void WriteStringIndentedPropertyName(ReadOnlySpan escapedPropertyN WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; @@ -375,7 +375,7 @@ private void WriteStringPropertyNameSection(ReadOnlySpan escapedPropertyNa private void WriteStringIndentedPropertyName(ReadOnlySpan escapedPropertyName) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length <= JsonConstants.MaxEscapedTokenSize); Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - 5 - s_newLineLength); @@ -402,7 +402,7 @@ private void WriteStringIndentedPropertyName(ReadOnlySpan escapedPropertyN WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; @@ -1513,7 +1513,7 @@ private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, ReadOn private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedValue.Length <= JsonConstants.MaxEscapedTokenSize); Debug.Assert(escapedPropertyName.Length < ((int.MaxValue - 7 - indent - s_newLineLength) / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length); @@ -1541,7 +1541,7 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnl WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; @@ -1563,7 +1563,7 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnl private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedValue.Length <= JsonConstants.MaxEscapedTokenSize); Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - escapedValue.Length - 7 - s_newLineLength); @@ -1590,7 +1590,7 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnl WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; @@ -1614,7 +1614,7 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnl private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedValue.Length <= JsonConstants.MaxEscapedTokenSize); Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 7 - indent - s_newLineLength); @@ -1642,7 +1642,7 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnl WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; @@ -1665,7 +1665,7 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnl private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedValue.Length <= JsonConstants.MaxEscapedTokenSize); Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 7 - indent - s_newLineLength); @@ -1693,7 +1693,7 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnl WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs index ab73c36582c640..37aad462665995 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs @@ -362,7 +362,7 @@ private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, ulong private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, ulong value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatUInt64Length - 5 - s_newLineLength); @@ -389,7 +389,7 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, ulong v WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; @@ -408,7 +408,7 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, ulong v private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, ulong value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatUInt64Length - 5 - s_newLineLength); @@ -434,7 +434,7 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, ulong v WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = JsonConstants.Quote; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Bytes.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Bytes.cs index 6d04df1c3ce6e6..31500fb2b00ec1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Bytes.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Bytes.cs @@ -88,7 +88,7 @@ private void WriteBase64Minimized(ReadOnlySpan bytes) private void WriteBase64Indented(ReadOnlySpan bytes) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); // Base64.GetMaxEncodedToUtf8Length checks to make sure the length is <= int.MaxValue / 4 * 3, // as a length longer than that would overflow int.MaxValue when Base64 encoded. However, we @@ -124,7 +124,7 @@ private void WriteBase64Indented(ReadOnlySpan bytes) { WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs index 7e3d3a30d661be..93f11451f454d7 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs @@ -105,7 +105,7 @@ private void WriteCommentMinimized(ReadOnlySpan value) private void WriteCommentIndented(ReadOnlySpan value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(value.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 4 - s_newLineLength); @@ -123,7 +123,7 @@ private void WriteCommentIndented(ReadOnlySpan value) if (_tokenType != JsonTokenType.None || _commentAfterNoneOrPropertyName) { WriteNewLine(output); - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; } @@ -212,7 +212,7 @@ private void WriteCommentMinimized(ReadOnlySpan utf8Value) private void WriteCommentIndented(ReadOnlySpan utf8Value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(utf8Value.Length < int.MaxValue - indent - 4 - s_newLineLength); @@ -230,7 +230,7 @@ private void WriteCommentIndented(ReadOnlySpan utf8Value) { WriteNewLine(output); - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs index f87753be0ae37d..ad8a887cdf55a0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs @@ -66,7 +66,7 @@ private void WriteStringValueMinimized(DateTime value) private void WriteStringValueIndented(DateTime value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); // 2 quotes, and optionally, 1 list separator and 1-2 bytes for new line int maxRequired = indent + JsonConstants.MaximumFormatDateTimeOffsetLength + 3 + s_newLineLength; @@ -89,7 +89,7 @@ private void WriteStringValueIndented(DateTime value) { WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs index 2608606375b396..1ee48004f21fd8 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs @@ -67,7 +67,7 @@ private void WriteStringValueMinimized(DateTimeOffset value) private void WriteStringValueIndented(DateTimeOffset value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); // 2 quotes, and optionally, 1 list separator and 1-2 bytes for new line int maxRequired = indent + JsonConstants.MaximumFormatDateTimeOffsetLength + 3 + s_newLineLength; @@ -90,7 +90,7 @@ private void WriteStringValueIndented(DateTimeOffset value) { WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs index 2462b667fe8ba1..35089030c2d454 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs @@ -63,7 +63,7 @@ private void WriteNumberValueMinimized(decimal value) private void WriteNumberValueIndented(decimal value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); int maxRequired = indent + JsonConstants.MaximumFormatDecimalLength + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line @@ -85,7 +85,7 @@ private void WriteNumberValueIndented(decimal value) { WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs index 036b9e3d122547..b638931ce12293 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs @@ -67,7 +67,7 @@ private void WriteNumberValueMinimized(double value) private void WriteNumberValueIndented(double value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); int maxRequired = indent + JsonConstants.MaximumFormatDoubleLength + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line @@ -89,7 +89,7 @@ private void WriteNumberValueIndented(double value) { WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs index 5e173a7dc0ecee..c1755c7c4dc5d3 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs @@ -67,7 +67,7 @@ private void WriteNumberValueMinimized(float value) private void WriteNumberValueIndented(float value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); int maxRequired = indent + JsonConstants.MaximumFormatSingleLength + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line @@ -89,7 +89,7 @@ private void WriteNumberValueIndented(float value) { WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs index f9005243fac4c6..f4c902e184af4c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs @@ -67,7 +67,7 @@ private void WriteNumberValueMinimized(ReadOnlySpan utf8Value) private void WriteNumberValueIndented(ReadOnlySpan utf8Value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(utf8Value.Length < int.MaxValue - indent - 1 - s_newLineLength); @@ -91,7 +91,7 @@ private void WriteNumberValueIndented(ReadOnlySpan utf8Value) { WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs index 227af26a3d2c00..695cc83e03680b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs @@ -67,7 +67,7 @@ private void WriteStringValueMinimized(Guid value) private void WriteStringValueIndented(Guid value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); // 2 quotes, and optionally, 1 list separator and 1-2 bytes for new line int maxRequired = indent + JsonConstants.MaximumFormatGuidLength + 3 + s_newLineLength; @@ -90,7 +90,7 @@ private void WriteStringValueIndented(Guid value) { WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs index b4bcc5b480246c..8c0dee44e785e4 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs @@ -86,7 +86,7 @@ private void WriteLiteralMinimized(ReadOnlySpan utf8Value) private void WriteLiteralIndented(ReadOnlySpan utf8Value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(utf8Value.Length <= 5); int maxRequired = indent + utf8Value.Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line @@ -109,7 +109,7 @@ private void WriteLiteralIndented(ReadOnlySpan utf8Value) { WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs index dd47dfe230550f..b2126de77fb2f1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs @@ -76,7 +76,7 @@ private void WriteNumberValueMinimized(long value) private void WriteNumberValueIndented(long value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); int maxRequired = indent + JsonConstants.MaximumFormatInt64Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line @@ -98,7 +98,7 @@ private void WriteNumberValueIndented(long value) { WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs index 6513d001930b77..4ede0fc9bce938 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs @@ -143,7 +143,7 @@ private void WriteStringMinimized(ReadOnlySpan escapedValue) private void WriteStringIndented(ReadOnlySpan escapedValue) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedValue.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 3 - s_newLineLength); @@ -169,7 +169,7 @@ private void WriteStringIndented(ReadOnlySpan escapedValue) { WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; } @@ -290,7 +290,7 @@ private void WriteStringMinimized(ReadOnlySpan escapedValue) private void WriteStringIndented(ReadOnlySpan escapedValue) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedValue.Length < int.MaxValue - indent - 3 - s_newLineLength); @@ -315,7 +315,7 @@ private void WriteStringIndented(ReadOnlySpan escapedValue) { WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs index a348c125c8172c..82ec4677b2224e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs @@ -78,7 +78,7 @@ private void WriteNumberValueMinimized(ulong value) private void WriteNumberValueIndented(ulong value) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); int maxRequired = indent + JsonConstants.MaximumFormatUInt64Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line @@ -100,7 +100,7 @@ private void WriteNumberValueIndented(ulong value) { WriteNewLine(output); } - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs index c84b864a0cfec6..315b7a30b8e207 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs @@ -58,6 +58,10 @@ public sealed partial class Utf8JsonWriter : IDisposable, IAsyncDisposable private JsonWriterOptions _options; // Since JsonWriterOptions is a struct, use a field to avoid a copy for internal code. + // Cache indentation settings from JsonWriterOptions to avoid recomputing them in the hot path. + private byte _indentByte; + private int _indentLength; + /// /// Returns the amount of bytes written by the so far /// that have not yet been flushed to the output and committed. @@ -80,7 +84,7 @@ public sealed partial class Utf8JsonWriter : IDisposable, IAsyncDisposable /// public JsonWriterOptions Options => _options; - private int Indentation => CurrentDepth * JsonConstants.SpacesPerIndent; + private int Indentation => CurrentDepth * _indentLength; internal JsonTokenType TokenType => _tokenType; @@ -112,12 +116,7 @@ public Utf8JsonWriter(IBufferWriter bufferWriter, JsonWriterOptions option } _output = bufferWriter; - _options = options; - - if (_options.MaxDepth == 0) - { - _options.MaxDepth = JsonWriterOptions.DefaultMaxDepth; // If max depth is not set, revert to the default depth. - } + SetOptions(options); } /// @@ -141,14 +140,21 @@ public Utf8JsonWriter(Stream utf8Json, JsonWriterOptions options = default) throw new ArgumentException(SR.StreamNotWritable); _stream = utf8Json; + SetOptions(options); + + _arrayBufferWriter = new ArrayBufferWriter(); + } + + private void SetOptions(JsonWriterOptions options) + { _options = options; + _indentByte = (byte)_options.IndentCharacter; + _indentLength = options.IndentSize; if (_options.MaxDepth == 0) { _options.MaxDepth = JsonWriterOptions.DefaultMaxDepth; // If max depth is not set, revert to the default depth. } - - _arrayBufferWriter = new ArrayBufferWriter(); } /// @@ -245,11 +251,7 @@ internal void Reset(IBufferWriter bufferWriter, JsonWriterOptions options) Debug.Assert(_output is null && _stream is null && _arrayBufferWriter is null); _output = bufferWriter; - _options = options; - if (_options.MaxDepth == 0) - { - _options.MaxDepth = JsonWriterOptions.DefaultMaxDepth; // If max depth is not set, revert to the default depth. - } + SetOptions(options); } internal static Utf8JsonWriter CreateEmptyInstanceForCaching() => new Utf8JsonWriter(); @@ -553,7 +555,7 @@ private void ValidateStart() private void WriteStartIndented(byte token) { int indent = Indentation; - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); int minRequired = indent + 1; // 1 start token int maxRequired = minRequired + 3; // Optionally, 1 list separator and 1-2 bytes for new line @@ -573,7 +575,7 @@ private void WriteStartIndented(byte token) if (_tokenType is not JsonTokenType.PropertyName and not JsonTokenType.None || _commentAfterNoneOrPropertyName) { WriteNewLine(output); - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; } @@ -994,10 +996,10 @@ private void WriteEndIndented(byte token) { // The end token should be at an outer indent and since we haven't updated // current depth yet, explicitly subtract here. - indent -= JsonConstants.SpacesPerIndent; + indent -= _indentLength; } - Debug.Assert(indent <= 2 * _options.MaxDepth); + Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.None); int maxRequired = indent + 3; // 1 end token, 1-2 bytes for new line @@ -1011,7 +1013,7 @@ private void WriteEndIndented(byte token) WriteNewLine(output); - JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + WriteIndentation(output.Slice(BytesPending), indent); BytesPending += indent; output[BytesPending++] = token; @@ -1029,6 +1031,11 @@ private void WriteNewLine(Span output) output[BytesPending++] = JsonConstants.LineFeed; } + private void WriteIndentation(Span buffer, int indent) + { + JsonWriterHelper.WriteIndentation(buffer, indent, _indentByte); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void UpdateBitStackOnStart(byte token) { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs index eb855b0241dfd5..9c1c30818f6b2d 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs @@ -78,6 +78,8 @@ public static void ContextWithAllOptionsSet_GeneratesExpectedOptions() UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode, UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow, WriteIndented = true, + IndentCharacter = '\t', + IndentSize = 1, TypeInfoResolver = ContextWithAllOptionsSet.Default, }; @@ -104,7 +106,9 @@ public static void ContextWithAllOptionsSet_GeneratesExpectedOptions() ReadCommentHandling = JsonCommentHandling.Skip, UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode, UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow, - WriteIndented = true)] + WriteIndented = true, + IndentCharacter = '\t', + IndentSize = 1)] [JsonSerializable(typeof(PersonStruct))] public partial class ContextWithAllOptionsSet : JsonSerializerContext { } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonWriterOptionsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonWriterOptionsTests.cs index 41a6c5c203a917..127ca9601150db 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonWriterOptionsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonWriterOptionsTests.cs @@ -15,6 +15,8 @@ public static void JsonWriterOptionsDefaultCtor() var expectedOption = new JsonWriterOptions { Indented = false, + IndentCharacter = ' ', + IndentSize = 2, SkipValidation = false, MaxDepth = 0, }; @@ -29,6 +31,8 @@ public static void JsonWriterOptionsCtor() var expectedOption = new JsonWriterOptions { Indented = false, + IndentCharacter = ' ', + IndentSize = 2, SkipValidation = false, MaxDepth = 0, }; @@ -36,26 +40,92 @@ public static void JsonWriterOptionsCtor() } [Theory] - [InlineData(true, true, 0)] - [InlineData(true, false, 1)] - [InlineData(false, true, 1024)] - [InlineData(false, false, 1024 * 1024)] - public static void JsonWriterOptions(bool indented, bool skipValidation, int maxDepth) + [InlineData(true, '\t', 1, true, 0)] + [InlineData(true, ' ', 127, false, 1)] + [InlineData(false, ' ', 0, true, 1024)] + [InlineData(false, ' ', 4, false, 1024 * 1024)] + public static void JsonWriterOptions(bool indented, char indentCharacter, int indentSize, bool skipValidation, int maxDepth) { var options = new JsonWriterOptions(); options.Indented = indented; + options.IndentCharacter = indentCharacter; + options.IndentSize = indentSize; options.SkipValidation = skipValidation; options.MaxDepth = maxDepth; var expectedOption = new JsonWriterOptions { Indented = indented, + IndentCharacter = indentCharacter, + IndentSize = indentSize, SkipValidation = skipValidation, MaxDepth = maxDepth, }; Assert.Equal(expectedOption, options); } + [Theory] + [InlineData(true, '\t', 1, true, 0)] + [InlineData(true, ' ', 127, false, 1)] + [InlineData(false, ' ', 0, true, 1024)] + [InlineData(false, ' ', 4, false, 1024 * 1024)] + public static void JsonWriterOptions_Properties(bool indented, char indentCharacter, int indentSize, bool skipValidation, int maxDepth) + { + var options = new JsonWriterOptions(); + options.Indented = indented; + options.IndentCharacter = indentCharacter; + options.IndentSize = indentSize; + options.SkipValidation = skipValidation; + options.MaxDepth = maxDepth; + + Assert.Equal(indented, options.Indented); + Assert.Equal(indentCharacter, options.IndentCharacter); + Assert.Equal(indentSize, options.IndentSize); + Assert.Equal(skipValidation, options.SkipValidation); + Assert.Equal(maxDepth, options.MaxDepth); + } + + [Fact] + public static void JsonWriterOptions_DefaultValues() + { + JsonWriterOptions options = default; + + Assert.False(options.Indented); + Assert.Equal(' ', options.IndentCharacter); + Assert.Equal(2, options.IndentSize); + Assert.False(options.SkipValidation); + Assert.Equal(0, options.MaxDepth); + } + + [Fact] + public static void JsonWriterOptions_MultipleValues() + { + JsonWriterOptions defaultOptions = default; + var options = new JsonWriterOptions(); + + options.Indented = true; + options.Indented = defaultOptions.Indented; + Assert.Equal(defaultOptions.Indented, options.Indented); + + options.IndentCharacter = '\t'; + options.IndentCharacter = defaultOptions.IndentCharacter; + Assert.Equal(defaultOptions.IndentCharacter, options.IndentCharacter); + + options.IndentSize = 127; + options.IndentSize = defaultOptions.IndentSize; + Assert.Equal(defaultOptions.IndentSize, options.IndentSize); + + options.SkipValidation = true; + options.SkipValidation = defaultOptions.SkipValidation; + Assert.Equal(defaultOptions.SkipValidation, options.SkipValidation); + + options.MaxDepth = 1024 * 1024; + options.MaxDepth = defaultOptions.MaxDepth; + Assert.Equal(defaultOptions.MaxDepth, options.MaxDepth); + + Assert.Equal(defaultOptions, options); + } + [Theory] [InlineData(-1)] [InlineData(-100)] @@ -64,5 +134,28 @@ public static void JsonWriterOptions_MaxDepth_InvalidParameters(int maxDepth) var options = new JsonWriterOptions(); Assert.Throws(() => options.MaxDepth = maxDepth); } + + [Theory] + [InlineData('\f')] + [InlineData('\n')] + [InlineData('\r')] + [InlineData('\0')] + [InlineData('a')] + public static void JsonWriterOptions_IndentCharacter_InvalidCharacter(char character) + { + var options = new JsonWriterOptions(); + Assert.Throws(() => options.IndentCharacter = character); + } + + [Theory] + [InlineData(-1)] + [InlineData(128)] + [InlineData(int.MinValue)] + [InlineData(int.MaxValue)] + public static void JsonWriterOptions_IndentSize_OutOfRange(int size) + { + var options = new JsonWriterOptions(); + Assert.Throws(() => options.IndentSize = size); + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CacheTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CacheTests.cs index 25c9e2084048bc..50c6d865268fdc 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CacheTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CacheTests.cs @@ -371,6 +371,8 @@ public static void JsonSerializerOptions_EqualityComparer_ChangingAnySettingShou yield return (GetProp(nameof(JsonSerializerOptions.ReadCommentHandling)), JsonCommentHandling.Skip); yield return (GetProp(nameof(JsonSerializerOptions.UnknownTypeHandling)), JsonUnknownTypeHandling.JsonNode); yield return (GetProp(nameof(JsonSerializerOptions.WriteIndented)), true); + yield return (GetProp(nameof(JsonSerializerOptions.IndentCharacter)), '\t'); + yield return (GetProp(nameof(JsonSerializerOptions.IndentSize)), 1); yield return (GetProp(nameof(JsonSerializerOptions.ReferenceHandler)), ReferenceHandler.Preserve); yield return (GetProp(nameof(JsonSerializerOptions.TypeInfoResolver)), new DefaultJsonTypeInfoResolver()); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs index 28bd0fb86cfe3a..cbca460362c4bc 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs @@ -330,6 +330,90 @@ public static void WriteIndented() Assert.Contains(Environment.NewLine, json); } + [Theory] + [InlineData('\f')] + [InlineData('\n')] + [InlineData('\r')] + [InlineData('\0')] + [InlineData('a')] + public static void IndentCharacters_WithInvalidChartacters_ThrowsArgumentOutOfRangeException(char character) + { + var options = new JsonSerializerOptions(); + Assert.Throws(() => options.IndentCharacter = character); + } + + [Theory] + [InlineData(-1)] + [InlineData(128)] + [InlineData(int.MinValue)] + [InlineData(int.MaxValue)] + public static void IndentSize_WithInvalidSize_ThrowsArgumentOutOfRangeException(int size) + { + var options = new JsonSerializerOptions(); + Assert.Throws(() => options.IndentSize = size); + } + + [Fact] + public static void IndentCharacter() + { + var obj = new BasicCompany(); + obj.Initialize(); + + // Verify default value. + var defaultIndent = " "; + string json = JsonSerializer.Serialize(obj); + Assert.DoesNotContain(defaultIndent, json); + + // Verify default value on options. + var options = new JsonSerializerOptions(); + json = JsonSerializer.Serialize(obj, options); + Assert.DoesNotContain(defaultIndent, json); + + // Enable default indentation. + options = new JsonSerializerOptions(); + options.WriteIndented = true; + json = JsonSerializer.Serialize(obj, options); + Assert.Contains(defaultIndent, json); + + // Set custom indentCharacter without enabling indentation + var tab = '\t'; + Assert.DoesNotContain(tab, json); + options = new JsonSerializerOptions(); + options.IndentCharacter = tab; + json = JsonSerializer.Serialize(obj, options); + Assert.DoesNotContain(tab, json); + + // Set custom indentCharacter with indentation enabled + options = new JsonSerializerOptions(); + options.WriteIndented = true; + options.IndentCharacter = tab; + json = JsonSerializer.Serialize(obj, options); + Assert.Contains(tab, json); + } + + [Fact] + public static void IndentSize() + { + var obj = new BasicCompany(); + obj.Initialize(); + + var tab = '\t'; + // Set custom indentSize without enabling indentation + var options = new JsonSerializerOptions(); + options.IndentCharacter = tab; + options.IndentSize = 1; + var json = JsonSerializer.Serialize(obj, options); + Assert.DoesNotContain(tab, json); + + // Set custom indentSize with indentation enabled + options = new JsonSerializerOptions(); + options.WriteIndented = true; + options.IndentCharacter = tab; + options.IndentSize = 4; + json = JsonSerializer.Serialize(obj, options); + Assert.Contains(new string(tab, 4), json); + } + [Fact] public static void ExtensionDataUsesReaderOptions() { @@ -1329,6 +1413,10 @@ and not nameof(JsonSerializerOptions.IsReadOnly)) // Property is not structural { property.SetValue(options, 32); } + else if (propertyType == typeof(string)) + { + property.SetValue(options, "\t"); + } else if (propertyType == typeof(IList)) { options.Converters.Add(new JsonStringEnumConverter()); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonWriterTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonWriterTests.cs index 331f81947ae5d5..643762183c713e 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonWriterTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonWriterTests.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.IO; using System.IO.Pipelines; +using System.Linq; using System.Text.Encodings.Web; using System.Text.Unicode; using System.Threading; @@ -64,15 +65,14 @@ public partial class Utf8JsonWriterTests /*Comment at start of doc*//*Multiple comment line*/{/*Comment before first object property*//*Multiple comment line*/"property1":/*Comment of string property value*/"stringValue","property2":/*Comment of object property value*/{}/*Comment in the middle of object*/,"property3":/*Comment of array property value*/[]/*Comment after the last property*/}/*Comment at end of doc*/ """; + public static IEnumerable JsonOptions_TestData() => + from options in JsonOptions() + select new object[] { options }; + [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void NullCtor(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void NullCtor(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - Assert.Throws(() => new Utf8JsonWriter((Stream)null)); Assert.Throws(() => new Utf8JsonWriter((IBufferWriter)null)); Assert.Throws(() => new Utf8JsonWriter((Stream)null, options)); @@ -80,13 +80,9 @@ public void NullCtor(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void CantWriteToNonWritableStream(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void CantWriteToNonWritableStream(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var stream = new MemoryStream(); stream.Dispose(); @@ -836,14 +832,9 @@ public void WriteLargeJsonToStreamWithoutFlushing() } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void InitialState(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void InitialState(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var stream = new MemoryStream(); using (var writer = new Utf8JsonWriter(stream, options)) { @@ -851,8 +842,8 @@ public void InitialState(bool formatted, bool skipValidation) Assert.Equal(0, writer.BytesPending); Assert.Equal(0, writer.CurrentDepth); Assert.Null(writer.Options.Encoder); - Assert.Equal(formatted, writer.Options.Indented); - Assert.Equal(skipValidation, writer.Options.SkipValidation); + Assert.Equal(options.Indented, writer.Options.Indented); + Assert.Equal(options.SkipValidation, writer.Options.SkipValidation); Assert.Equal(0, stream.Position); } @@ -863,21 +854,16 @@ public void InitialState(bool formatted, bool skipValidation) Assert.Equal(0, writer.BytesPending); Assert.Equal(0, writer.CurrentDepth); Assert.Null(writer.Options.Encoder); - Assert.Equal(formatted, writer.Options.Indented); - Assert.Equal(skipValidation, writer.Options.SkipValidation); + Assert.Equal(options.Indented, writer.Options.Indented); + Assert.Equal(options.SkipValidation, writer.Options.SkipValidation); Assert.Equal(0, output.FormattedCount); } } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void Reset(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void Reset(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var stream = new MemoryStream(); using var writeToStream = new Utf8JsonWriter(stream, options); writeToStream.WriteNumberValue(1); @@ -890,8 +876,8 @@ public void Reset(bool formatted, bool skipValidation) Assert.Equal(0, writeToStream.BytesPending); Assert.Equal(0, writeToStream.CurrentDepth); Assert.Null(writeToStream.Options.Encoder); - Assert.Equal(formatted, writeToStream.Options.Indented); - Assert.Equal(skipValidation, writeToStream.Options.SkipValidation); + Assert.Equal(options.Indented, writeToStream.Options.Indented); + Assert.Equal(options.SkipValidation, writeToStream.Options.SkipValidation); Assert.True(stream.Position != 0); long previousWritten = stream.Position; @@ -910,8 +896,8 @@ public void Reset(bool formatted, bool skipValidation) Assert.Equal(0, writeToIBW.BytesPending); Assert.Equal(0, writeToIBW.CurrentDepth); Assert.Null(writeToIBW.Options.Encoder); - Assert.Equal(formatted, writeToIBW.Options.Indented); - Assert.Equal(skipValidation, writeToIBW.Options.SkipValidation); + Assert.Equal(options.Indented, writeToIBW.Options.Indented); + Assert.Equal(options.SkipValidation, writeToIBW.Options.SkipValidation); Assert.True(output.FormattedCount != 0); previousWritten = output.FormattedCount; @@ -920,14 +906,9 @@ public void Reset(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void ResetWithSameOutput(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void ResetWithSameOutput(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var stream = new MemoryStream(); using var writeToStream = new Utf8JsonWriter(stream, options); writeToStream.WriteNumberValue(1); @@ -940,8 +921,8 @@ public void ResetWithSameOutput(bool formatted, bool skipValidation) Assert.Equal(0, writeToStream.BytesPending); Assert.Equal(0, writeToStream.CurrentDepth); Assert.Null(writeToStream.Options.Encoder); - Assert.Equal(formatted, writeToStream.Options.Indented); - Assert.Equal(skipValidation, writeToStream.Options.SkipValidation); + Assert.Equal(options.Indented, writeToStream.Options.Indented); + Assert.Equal(options.SkipValidation, writeToStream.Options.SkipValidation); Assert.True(stream.Position != 0); long previousWritten = stream.Position; @@ -966,8 +947,8 @@ public void ResetWithSameOutput(bool formatted, bool skipValidation) Assert.Equal(0, writeToIBW.BytesPending); Assert.Equal(0, writeToIBW.CurrentDepth); Assert.Null(writeToIBW.Options.Encoder); - Assert.Equal(formatted, writeToIBW.Options.Indented); - Assert.Equal(skipValidation, writeToIBW.Options.SkipValidation); + Assert.Equal(options.Indented, writeToIBW.Options.Indented); + Assert.Equal(options.SkipValidation, writeToIBW.Options.SkipValidation); Assert.True(output.FormattedCount != 0); previousWritten = output.FormattedCount; @@ -982,14 +963,9 @@ public void ResetWithSameOutput(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void ResetChangeOutputMode(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void ResetChangeOutputMode(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var stream = new MemoryStream(); using var writeToStream = new Utf8JsonWriter(stream, options); writeToStream.WriteNumberValue(1); @@ -1003,8 +979,8 @@ public void ResetChangeOutputMode(bool formatted, bool skipValidation) Assert.Equal(0, writeToStream.BytesPending); Assert.Equal(0, writeToStream.CurrentDepth); Assert.Null(writeToStream.Options.Encoder); - Assert.Equal(formatted, writeToStream.Options.Indented); - Assert.Equal(skipValidation, writeToStream.Options.SkipValidation); + Assert.Equal(options.Indented, writeToStream.Options.Indented); + Assert.Equal(options.SkipValidation, writeToStream.Options.SkipValidation); Assert.True(stream.Position != 0); long previousWrittenStream = stream.Position; @@ -1034,8 +1010,8 @@ public void ResetChangeOutputMode(bool formatted, bool skipValidation) Assert.Equal(0, writeToIBW.BytesPending); Assert.Equal(0, writeToIBW.CurrentDepth); Assert.Null(writeToIBW.Options.Encoder); - Assert.Equal(formatted, writeToIBW.Options.Indented); - Assert.Equal(skipValidation, writeToIBW.Options.SkipValidation); + Assert.Equal(options.Indented, writeToIBW.Options.Indented); + Assert.Equal(options.SkipValidation, writeToIBW.Options.SkipValidation); Assert.True(output.FormattedCount != 0); previousWrittenStream = stream.Position; @@ -1054,14 +1030,9 @@ public void ResetChangeOutputMode(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void InvalidReset(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void InvalidReset(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var stream = new MemoryStream(); using var writeToStream = new Utf8JsonWriter(stream, options); @@ -1082,13 +1053,9 @@ public void InvalidReset(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void FlushEmpty(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void FlushEmpty(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(0); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -1104,13 +1071,9 @@ public void FlushEmpty(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public async Task FlushEmptyAsync(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public async Task FlushEmptyAsync(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(0); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -1126,13 +1089,9 @@ public async Task FlushEmptyAsync(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void FlushMultipleTimes(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void FlushMultipleTimes(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(256); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -1168,13 +1127,9 @@ public void FlushMultipleTimes(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public async Task FlushMultipleTimesAsync(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public async Task FlushMultipleTimesAsync(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(256); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -1210,13 +1165,9 @@ public async Task FlushMultipleTimesAsync(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void DisposeAutoFlushes(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void DisposeAutoFlushes(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(256); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -1240,13 +1191,9 @@ public void DisposeAutoFlushes(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public async Task DisposeAutoFlushesAsync(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public async Task DisposeAutoFlushesAsync(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(256); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -1270,13 +1217,9 @@ public async Task DisposeAutoFlushesAsync(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void UseAfterDisposeInvalid(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void UseAfterDisposeInvalid(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(256); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -1316,13 +1259,9 @@ public void UseAfterDisposeInvalid(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public async Task UseAfterDisposeInvalidAsync(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public async Task UseAfterDisposeInvalidAsync(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(256); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -1419,13 +1358,9 @@ public override async Task WriteAsync(byte[] buffer, int offset, int count, Canc } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void InvalidBufferWriter(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void InvalidBufferWriter(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new InvalidBufferWriter(); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -1434,34 +1369,26 @@ public void InvalidBufferWriter(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public async Task WriteLargeToStream(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public async Task WriteLargeToStream(JsonWriterOptions options) { var stream = new MemoryStream(); - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - + await WriteLargeToStreamHelper(stream, options); - string expectedString = GetExpectedLargeString(formatted); + string expectedString = GetExpectedLargeString(options); string actualString = Encoding.UTF8.GetString(stream.ToArray()); Assert.Equal(expectedString, actualString); } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void GrowBeyondBufferSize(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void GrowBeyondBufferSize(JsonWriterOptions options) { const int InitialGrowthSize = 256; var output = new FixedSizedBufferWriter(InitialGrowthSize); - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - + byte[] utf8String = "this is a string long enough to overflow the buffer and cause an exception to be thrown."u8.ToArray(); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -1496,14 +1423,14 @@ private static async Task WriteLargeToStreamHelper(Stream stream, JsonWriterOpti jsonUtf8.WriteEndArray(); } - private static string GetExpectedLargeString(bool prettyPrint) + private static string GetExpectedLargeString(JsonWriterOptions options) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None + Formatting = options.Indented ? Formatting.Indented : Formatting.None }; json.WriteStartArray(); @@ -1515,19 +1442,15 @@ private static string GetExpectedLargeString(bool prettyPrint) json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void FixedSizeBufferWriter_Guid(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void FixedSizeBufferWriter_Guid(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - - int sizeTooSmall = 256; + int sizeTooSmall = options.Indented ? options.IndentSize + 229 : 225; + sizeTooSmall = Math.Max(sizeTooSmall, 256); var output = new FixedSizedBufferWriter(sizeTooSmall); byte[] utf8String = Encoding.UTF8.GetBytes(new string('a', 215)); @@ -1541,7 +1464,7 @@ public void FixedSizeBufferWriter_Guid(bool formatted, bool skipValidation) Assert.Throws(() => jsonUtf8.WriteStringValue(guid)); } - sizeTooSmall += formatted ? 9 : 1; + sizeTooSmall += options.Indented ? options.IndentSize + 40 : 38; output = new FixedSizedBufferWriter(sizeTooSmall); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { @@ -1552,7 +1475,7 @@ public void FixedSizeBufferWriter_Guid(bool formatted, bool skipValidation) } string actualStr = Encoding.UTF8.GetString(output.Formatted); - if (!formatted) + if (!options.Indented) { Assert.Equal(257, output.Formatted.Length); } @@ -1560,15 +1483,11 @@ public void FixedSizeBufferWriter_Guid(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void FixedSizeBufferWriter_DateTime(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void FixedSizeBufferWriter_DateTime(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - - int sizeTooSmall = 256; + int sizeTooSmall = options.Indented ? options.IndentSize + 240 : 236; + sizeTooSmall = Math.Max(sizeTooSmall, 256); var output = new FixedSizedBufferWriter(sizeTooSmall); byte[] utf8String = Encoding.UTF8.GetBytes(new string('a', 232)); @@ -1582,7 +1501,7 @@ public void FixedSizeBufferWriter_DateTime(bool formatted, bool skipValidation) Assert.Throws(() => jsonUtf8.WriteStringValue(date)); } - sizeTooSmall += formatted ? 23 : 15; + sizeTooSmall += options.Indented ? options.IndentSize + 37 : 35; output = new FixedSizedBufferWriter(sizeTooSmall); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { @@ -1593,7 +1512,7 @@ public void FixedSizeBufferWriter_DateTime(bool formatted, bool skipValidation) } string actualStr = Encoding.UTF8.GetString(output.Formatted); - if (!formatted) + if (!options.Indented) { Assert.Equal(257, output.Formatted.Length); } @@ -1601,15 +1520,11 @@ public void FixedSizeBufferWriter_DateTime(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void FixedSizeBufferWriter_DateTimeOffset(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void FixedSizeBufferWriter_DateTimeOffset(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - - int sizeTooSmall = 256; + int sizeTooSmall = options.Indented ? options.IndentSize + 240 : 236; + sizeTooSmall = Math.Max(sizeTooSmall, 256); var output = new FixedSizedBufferWriter(sizeTooSmall); byte[] utf8String = Encoding.UTF8.GetBytes(new string('a', 226)); @@ -1623,7 +1538,7 @@ public void FixedSizeBufferWriter_DateTimeOffset(bool formatted, bool skipValida Assert.Throws(() => jsonUtf8.WriteStringValue(date)); } - sizeTooSmall += formatted ? 23 : 15; + sizeTooSmall += options.Indented ? options.IndentSize + 37 : 35; output = new FixedSizedBufferWriter(sizeTooSmall); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { @@ -1634,7 +1549,7 @@ public void FixedSizeBufferWriter_DateTimeOffset(bool formatted, bool skipValida } string actualStr = Encoding.UTF8.GetString(output.Formatted); - if (!formatted) + if (!options.Indented) { Assert.Equal(257, output.Formatted.Length); } @@ -1642,13 +1557,9 @@ public void FixedSizeBufferWriter_DateTimeOffset(bool formatted, bool skipValida } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void FixedSizeBufferWriter_Decimal(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void FixedSizeBufferWriter_Decimal(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var random = new Random(42); for (int i = 0; i < 1_000; i++) @@ -1707,7 +1618,8 @@ public void FixedSizeBufferWriter_Decimal(bool formatted, bool skipValidation) } { - int sizeTooSmall = 256; + int sizeTooSmall = options.Indented ? options.IndentSize + 230 : 226; + sizeTooSmall = Math.Max(sizeTooSmall, 256); var output = new FixedSizedBufferWriter(sizeTooSmall); byte[] utf8String = Encoding.UTF8.GetBytes(new string('a', 222)); @@ -1721,7 +1633,7 @@ public void FixedSizeBufferWriter_Decimal(bool formatted, bool skipValidation) Assert.Throws(() => jsonUtf8.WriteNumberValue(value)); } - sizeTooSmall += formatted ? 9 : 1; + sizeTooSmall += options.Indented ? options.IndentSize + 33 : 31; output = new FixedSizedBufferWriter(sizeTooSmall); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { @@ -1733,7 +1645,7 @@ public void FixedSizeBufferWriter_Decimal(bool formatted, bool skipValidation) } string actualStr = Encoding.UTF8.GetString(output.Formatted); - if (!formatted) + if (!options.Indented) { Assert.Equal(257, output.Formatted.Length); } @@ -1741,134 +1653,117 @@ public void FixedSizeBufferWriter_Decimal(bool formatted, bool skipValidation) } } + public static IEnumerable InvalidJsonDueToWritingMultipleValues_TestData() => + JsonOptionsWith([ + JsonValueKind.Array, + JsonValueKind.Object, + JsonValueKind.String, + JsonValueKind.Number, + JsonValueKind.True, + JsonValueKind.False, + JsonValueKind.Null, + ]); + [Theory] - [InlineData(JsonValueKind.Array, true, true)] - [InlineData(JsonValueKind.Array, true, false)] - [InlineData(JsonValueKind.Array, false, true)] - [InlineData(JsonValueKind.Array, false, false)] - [InlineData(JsonValueKind.Object, true, true)] - [InlineData(JsonValueKind.Object, true, false)] - [InlineData(JsonValueKind.Object, false, true)] - [InlineData(JsonValueKind.Object, false, false)] - [InlineData(JsonValueKind.String, true, true)] - [InlineData(JsonValueKind.String, true, false)] - [InlineData(JsonValueKind.String, false, true)] - [InlineData(JsonValueKind.String, false, false)] - [InlineData(JsonValueKind.Number, true, true)] - [InlineData(JsonValueKind.Number, true, false)] - [InlineData(JsonValueKind.Number, false, true)] - [InlineData(JsonValueKind.Number, false, false)] - [InlineData(JsonValueKind.True, true, true)] - [InlineData(JsonValueKind.True, true, false)] - [InlineData(JsonValueKind.True, false, true)] - [InlineData(JsonValueKind.True, false, false)] - [InlineData(JsonValueKind.False, true, true)] - [InlineData(JsonValueKind.False, true, false)] - [InlineData(JsonValueKind.False, false, true)] - [InlineData(JsonValueKind.False, false, false)] - [InlineData(JsonValueKind.Null, true, true)] - [InlineData(JsonValueKind.Null, true, false)] - [InlineData(JsonValueKind.Null, false, true)] - [InlineData(JsonValueKind.Null, false, false)] - public void InvalidJsonDueToWritingMultipleValues(JsonValueKind kind, bool formatted, bool skipValidation) - { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + [MemberData(nameof(InvalidJsonDueToWritingMultipleValues_TestData))] + public void InvalidJsonDueToWritingMultipleValues(JsonWriterOptions options, JsonValueKind kind) + { var output = new ArrayBufferWriter(1024); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteStartObject(), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteStartObject(), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteStartObject("foo"), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteStartObject("foo"), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteStartArray(), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteStartArray(), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteEndObject(), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteEndObject(), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteEndArray(), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteEndArray(), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind); - ValidateAction(jsonUtf8, () => jsonUtf8.WritePropertyName("foo"), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WritePropertyName("foo"), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteString("key", "foo"), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteString("key", "foo"), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteStringValue("foo"), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteStringValue("foo"), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteNumber("key", 123), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteNumber("key", 123), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteNumberValue(123), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteNumberValue(123), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteBoolean("key", true), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteBoolean("key", true), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteBooleanValue(true), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteBooleanValue(true), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteBoolean("key", false), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteBoolean("key", false), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteBooleanValue(false), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteBooleanValue(false), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteNull("key"), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteNull("key"), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteNullValue(), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteNullValue(), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) @@ -1880,133 +1775,105 @@ public void InvalidJsonDueToWritingMultipleValues(JsonValueKind kind, bool forma } [Theory] - [InlineData(JsonValueKind.Array, true, true)] - [InlineData(JsonValueKind.Array, true, false)] - [InlineData(JsonValueKind.Array, false, true)] - [InlineData(JsonValueKind.Array, false, false)] - [InlineData(JsonValueKind.Object, true, true)] - [InlineData(JsonValueKind.Object, true, false)] - [InlineData(JsonValueKind.Object, false, true)] - [InlineData(JsonValueKind.Object, false, false)] - [InlineData(JsonValueKind.String, true, true)] - [InlineData(JsonValueKind.String, true, false)] - [InlineData(JsonValueKind.String, false, true)] - [InlineData(JsonValueKind.String, false, false)] - [InlineData(JsonValueKind.Number, true, true)] - [InlineData(JsonValueKind.Number, true, false)] - [InlineData(JsonValueKind.Number, false, true)] - [InlineData(JsonValueKind.Number, false, false)] - [InlineData(JsonValueKind.True, true, true)] - [InlineData(JsonValueKind.True, true, false)] - [InlineData(JsonValueKind.True, false, true)] - [InlineData(JsonValueKind.True, false, false)] - [InlineData(JsonValueKind.False, true, true)] - [InlineData(JsonValueKind.False, true, false)] - [InlineData(JsonValueKind.False, false, true)] - [InlineData(JsonValueKind.False, false, false)] - [InlineData(JsonValueKind.Null, true, true)] - [InlineData(JsonValueKind.Null, true, false)] - [InlineData(JsonValueKind.Null, false, true)] - [InlineData(JsonValueKind.Null, false, false)] - public void InvalidJsonDueToWritingMultipleValuesWithComments(JsonValueKind kind, bool formatted, bool skipValidation) - { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + [MemberData(nameof(InvalidJsonDueToWritingMultipleValues_TestData))] + public void InvalidJsonDueToWritingMultipleValuesWithComments(JsonWriterOptions options, JsonValueKind kind) + { var output = new ArrayBufferWriter(1024); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind, addComments: true); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteStartObject(), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteStartObject(), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind, addComments: true); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteStartObject("foo"), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteStartObject("foo"), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind, addComments: true); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteStartArray(), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteStartArray(), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind, addComments: true); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteEndObject(), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteEndObject(), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind, addComments: true); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteEndArray(), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteEndArray(), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind, addComments: true); - ValidateAction(jsonUtf8, () => jsonUtf8.WritePropertyName("foo"), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WritePropertyName("foo"), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind, addComments: true); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteString("key", "foo"), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteString("key", "foo"), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind, addComments: true); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteStringValue("foo"), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteStringValue("foo"), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind, addComments: true); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteNumber("key", 123), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteNumber("key", 123), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind, addComments: true); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteNumberValue(123), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteNumberValue(123), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind, addComments: true); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteBoolean("key", true), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteBoolean("key", true), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind, addComments: true); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteBooleanValue(true), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteBooleanValue(true), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind, addComments: true); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteBoolean("key", false), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteBoolean("key", false), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind, addComments: true); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteBooleanValue(false), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteBooleanValue(false), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind, addComments: true); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteNull("key"), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteNull("key"), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { WritePreamble(jsonUtf8, kind, addComments: true); - ValidateAction(jsonUtf8, () => jsonUtf8.WriteNullValue(), skipValidation); + ValidateAction(jsonUtf8, () => jsonUtf8.WriteNullValue(), options.SkipValidation); } using (var jsonUtf8 = new Utf8JsonWriter(output, options)) @@ -2078,18 +1945,14 @@ private void ValidateAction(Utf8JsonWriter writer, Action action, bool skipValid } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void InvalidJsonMismatch(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void InvalidJsonMismatch(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new ArrayBufferWriter(1024); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteEndArray(); } @@ -2101,7 +1964,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteEndObject(); } @@ -2113,7 +1976,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteStartArray("property at start"); } @@ -2125,7 +1988,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteStartObject("property at start"); } @@ -2138,7 +2001,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteStartArray(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteStartArray("property inside array"); } @@ -2151,7 +2014,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteStartObject(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteStartObject(); } @@ -2164,7 +2027,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteStartArray(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteEndObject(); } @@ -2177,7 +2040,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteStartObject(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteStringValue("key"); } @@ -2190,7 +2053,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteStartArray(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteString("key", "value"); } @@ -2203,7 +2066,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteStartArray(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteString(JsonEncodedText.Encode("key"), JsonEncodedText.Encode("value")); } @@ -2216,7 +2079,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteStartObject(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteEndArray(); } @@ -2229,7 +2092,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteStartObject(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteStartArray(); } @@ -2244,7 +2107,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) jsonUtf8.WriteStartArray(); jsonUtf8.WriteStartArray(); jsonUtf8.WriteEndArray(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteEndObject(); } @@ -2259,7 +2122,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) jsonUtf8.WriteStartObject(); jsonUtf8.WriteStartObject("some object"); jsonUtf8.WriteEndObject(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteEndArray(); } @@ -2272,7 +2135,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteStartArray(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteStartObject("some object"); jsonUtf8.WriteEndObject(); @@ -2289,7 +2152,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) jsonUtf8.WriteStartObject(); jsonUtf8.WriteStartArray("test array"); jsonUtf8.WriteEndArray(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteEndArray(); } @@ -2303,7 +2166,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) { jsonUtf8.WriteStartArray(); jsonUtf8.WriteEndArray(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteEndArray(); } @@ -2317,7 +2180,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) { jsonUtf8.WriteStartObject(); jsonUtf8.WriteEndObject(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteEndObject(); } @@ -2331,7 +2194,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) { jsonUtf8.WriteStartArray(); jsonUtf8.WriteStartArray(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteEndObject(); } @@ -2345,7 +2208,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) { jsonUtf8.WriteStartObject(); jsonUtf8.WriteStartObject("test object"); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteEndArray(); } @@ -2357,7 +2220,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WritePropertyName("test name"); jsonUtf8.WritePropertyName(JsonEncodedText.Encode("test name")); @@ -2376,7 +2239,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteStartArray(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WritePropertyName("test name"); } @@ -2390,7 +2253,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) { jsonUtf8.WriteStartObject(); jsonUtf8.WritePropertyName("first name"); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WritePropertyName("test name"); } @@ -2404,7 +2267,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) { jsonUtf8.WriteStartObject(); jsonUtf8.WritePropertyName("first name"); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteStartArray("test name"); } @@ -2418,7 +2281,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) { jsonUtf8.WriteStartObject(); jsonUtf8.WritePropertyName("first name"); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteStartObject("test name"); } @@ -2432,7 +2295,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) { jsonUtf8.WriteStartObject(); jsonUtf8.WritePropertyName("first name"); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteEndObject(); } @@ -2446,7 +2309,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) { jsonUtf8.WriteStartObject(); jsonUtf8.WritePropertyName("first name"); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteString("another property name", "some value"); } @@ -2460,7 +2323,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) { jsonUtf8.WriteStartObject(); jsonUtf8.WritePropertyName("first name"); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteNumber("another property name", 12345); } @@ -2474,7 +2337,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) { jsonUtf8.WriteStartObject(); jsonUtf8.WritePropertyName("first name"); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteNull("another property name"); } @@ -2488,7 +2351,7 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) { jsonUtf8.WriteStartObject(); jsonUtf8.WritePropertyName("first name"); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteBoolean("another property name", true); } @@ -2500,13 +2363,9 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void InvalidJsonIncomplete(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void InvalidJsonIncomplete(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new ArrayBufferWriter(1024); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) @@ -2554,19 +2413,15 @@ public void InvalidJsonIncomplete(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void InvalidJsonPrimitive(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void InvalidJsonPrimitive(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new ArrayBufferWriter(1024); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteNumberValue(12345); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteNumberValue(12345); } @@ -2579,7 +2434,7 @@ public void InvalidJsonPrimitive(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteNumberValue(12345); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteStartArray(); } @@ -2592,7 +2447,7 @@ public void InvalidJsonPrimitive(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteNumberValue(12345); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteStartObject(); } @@ -2605,7 +2460,7 @@ public void InvalidJsonPrimitive(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteNumberValue(12345); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteStartArray("property name"); } @@ -2618,7 +2473,7 @@ public void InvalidJsonPrimitive(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteNumberValue(12345); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteStartObject("property name"); } @@ -2631,7 +2486,7 @@ public void InvalidJsonPrimitive(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteNumberValue(12345); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteString("property name", "value"); } @@ -2644,7 +2499,7 @@ public void InvalidJsonPrimitive(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteNumberValue(12345); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteString(JsonEncodedText.Encode("property name"), JsonEncodedText.Encode("value")); } @@ -2657,7 +2512,7 @@ public void InvalidJsonPrimitive(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteNumberValue(12345); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteEndArray(); } @@ -2670,7 +2525,7 @@ public void InvalidJsonPrimitive(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteNumberValue(12345); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteEndObject(); } @@ -2683,7 +2538,7 @@ public void InvalidJsonPrimitive(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteNumberValue(12345); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WritePropertyName("test name"); } @@ -2696,7 +2551,7 @@ public void InvalidJsonPrimitive(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteBooleanValue(true); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WritePropertyName("test name"); } @@ -2709,7 +2564,7 @@ public void InvalidJsonPrimitive(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteNullValue(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WritePropertyName("test name"); } @@ -2722,7 +2577,7 @@ public void InvalidJsonPrimitive(bool formatted, bool skipValidation) using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteStringValue("some string"); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WritePropertyName("test name"); } @@ -2734,13 +2589,9 @@ public void InvalidJsonPrimitive(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void InvalidNumbersJson(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void InvalidNumbersJson(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new ArrayBufferWriter(1024); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) @@ -2831,12 +2682,12 @@ public void InvalidJsonContinueShouldSucceed(bool formatted) var sb = new StringBuilder(); for (int i = 0; i < 100; i++) { - if (formatted) + if (options.Indented) sb.Append(Environment.NewLine); sb.Append("]"); } sb.Append(","); - if (formatted) + if (options.Indented) sb.Append(Environment.NewLine); sb.Append("[]"); @@ -2844,19 +2695,15 @@ public void InvalidJsonContinueShouldSucceed(bool formatted) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteSeparateProperties(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteSeparateProperties(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new ArrayBufferWriter(1024); var stringWriter = new StringWriter(); var json = new JsonTextWriter(stringWriter) { - Formatting = formatted ? Formatting.Indented : Formatting.None, + Formatting = options.Indented ? Formatting.Indented : Formatting.None, }; json.WriteStartObject(); @@ -2875,7 +2722,7 @@ public void WriteSeparateProperties(bool formatted, bool skipValidation) json.Flush(); - string expectedStr = stringWriter.ToString(); + string expectedStr = HandleIndent(stringWriter.ToString(), options); using var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); @@ -2896,13 +2743,9 @@ public void WriteSeparateProperties(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WritingTooDeep(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WritingTooDeep(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new ArrayBufferWriter(1024); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -2916,15 +2759,11 @@ public void WritingTooDeep(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WritingTooDeepProperty(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WritingTooDeepProperty(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(1024); - + var capacity = 3 + 1000 * (11 + 1001 * options.IndentSize / 2); + var output = new ArrayBufferWriter(capacity); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteStartObject(); @@ -2936,6 +2775,7 @@ public void WritingTooDeepProperty(bool formatted, bool skipValidation) Assert.Throws(() => jsonUtf8.WriteStartArray("name")); } + output = new ArrayBufferWriter(capacity); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteStartObject(); @@ -2946,6 +2786,7 @@ public void WritingTooDeepProperty(bool formatted, bool skipValidation) Assert.Throws(() => jsonUtf8.WriteStartArray("name"u8)); } + output = new ArrayBufferWriter(capacity); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { jsonUtf8.WriteStartObject(); @@ -3017,11 +2858,8 @@ public static void CustomMaxDepth_DepthExceedingLimit_ShouldFail(int maxDepth) [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.OSX)] [ConditionalTheory(typeof(Environment), nameof(Environment.Is64BitProcess))] [OuterLoop] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WritingTooLargeProperty(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WritingTooLargeProperty(JsonWriterOptions options) { try { @@ -3034,8 +2872,7 @@ public void WritingTooLargeProperty(bool formatted, bool skipValidation) key.AsSpan().Fill((byte)'a'); keyChars.AsSpan().Fill('a'); - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(1024); + var output = new ArrayBufferWriter(1024); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { @@ -3062,11 +2899,8 @@ public void WritingTooLargeProperty(bool formatted, bool skipValidation) [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.OSX)] [ConditionalTheory(typeof(Environment), nameof(Environment.Is64BitProcess))] [OuterLoop] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WritingTooLargePropertyStandalone(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WritingTooLargePropertyStandalone(JsonWriterOptions options) { try { @@ -3079,8 +2913,7 @@ public void WritingTooLargePropertyStandalone(bool formatted, bool skipValidatio key.AsSpan().Fill((byte)'a'); keyChars.AsSpan().Fill('a'); - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(1024); + var output = new ArrayBufferWriter(1024); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { @@ -3102,19 +2935,15 @@ public void WritingTooLargePropertyStandalone(bool formatted, bool skipValidatio [ConditionalTheory(typeof(Environment), nameof(Environment.Is64BitProcess))] [OuterLoop] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WritingTooLargeBase64Bytes(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WritingTooLargeBase64Bytes(JsonWriterOptions options) { try { byte[] value = new byte[200_000_000]; value.AsSpan().Fill((byte)'a'); - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(value.Length); + var output = new ArrayBufferWriter(value.Length); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { @@ -3185,11 +3014,8 @@ public void WritingTooLargeBase64Bytes(bool formatted, bool skipValidation) [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.OSX)] [ConditionalTheory(typeof(Environment), nameof(Environment.Is64BitProcess))] [OuterLoop] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WritingHugeBase64Bytes(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WritingHugeBase64Bytes(JsonWriterOptions options) { try { @@ -3197,8 +3023,7 @@ public void WritingHugeBase64Bytes(bool formatted, bool skipValidation) value.AsSpan().Fill(168); - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(1024); + var output = new ArrayBufferWriter(1024); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) { @@ -3245,12 +3070,9 @@ public void WritingHugeBase64Bytes(bool formatted, bool skipValidation) // https://github.com/dotnet/runtime/issues/30746 [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] + [MemberData(nameof(JsonOptions_TestData))] [SkipOnCoreClr("https://github.com/dotnet/runtime/issues/45464", ~RuntimeConfiguration.Release)] - public void Writing3MBBase64Bytes(bool formatted, bool skipValidation) + public void Writing3MBBase64Bytes(JsonWriterOptions options) { byte[] value = new byte[3 * 1024 * 1024]; @@ -3260,9 +3082,8 @@ public void Writing3MBBase64Bytes(bool formatted, bool skipValidation) Base64.EncodeToUtf8(value, base64StringUtf8, out _, out int bytesWritten); string expectedValue = Encoding.UTF8.GetString(base64StringUtf8.AsSpan(0, bytesWritten).ToArray()); - string expectedJson = formatted ? $"{{{Environment.NewLine} \"foo\": \"{expectedValue}\"{Environment.NewLine}}}" : $"{{\"foo\":\"{expectedValue}\"}}"; + string expectedJson = options.Indented ? $"{{{Environment.NewLine}{GetIndentText(options)}\"foo\": \"{expectedValue}\"{Environment.NewLine}}}" : $"{{\"foo\":\"{expectedValue}\"}}"; - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new ArrayBufferWriter(1024); using (var jsonUtf8 = new Utf8JsonWriter(output, options)) @@ -3309,16 +3130,12 @@ public void Writing3MBBase64Bytes(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteSingleValue(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteSingleValue(JsonWriterOptions options) { string expectedStr = "123456789012345"; - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - + var output = new ArrayBufferWriter(1024); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -3330,15 +3147,12 @@ public void WriteSingleValue(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteHelloWorld(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteHelloWorld(JsonWriterOptions options) { string propertyName = "message"; string value = "Hello, World!"; - string expectedStr = GetHelloWorldExpectedString(prettyPrint: formatted, propertyName, value); + string expectedStr = GetHelloWorldExpectedString(options, propertyName, value); JsonEncodedText encodedPropertyName = JsonEncodedText.Encode(propertyName); JsonEncodedText encodedValue = JsonEncodedText.Encode(value); @@ -3346,8 +3160,7 @@ public void WriteHelloWorld(bool formatted, bool skipValidation) ReadOnlySpan utf8PropertyName = "message"u8; ReadOnlySpan utf8Value = "Hello, World!"u8; - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - + for (int i = 0; i < 32; i++) { var output = new ArrayBufferWriter(32); @@ -3527,18 +3340,13 @@ public void WriteHelloWorld(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteHelloWorldEscaped(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteHelloWorldEscaped(JsonWriterOptions options) { string propertyName = "mess> propertyNameSpan = propertyName.AsSpan(); ReadOnlySpan valueSpan = value.AsSpan(); ReadOnlySpan propertyNameSpanUtf8 = Encoding.UTF8.GetBytes(propertyName); @@ -3734,14 +3542,10 @@ public void WriteHelloWorldEscaped(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WritePartialHelloWorld(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WritePartialHelloWorld(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - + var output = new ArrayBufferWriter(10); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -3753,15 +3557,15 @@ public void WritePartialHelloWorld(bool formatted, bool skipValidation) jsonUtf8.WriteString("message", "Hello, World!"); Assert.Equal(0, jsonUtf8.BytesCommitted); - if (formatted) - Assert.Equal(26 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesPending); // new lines, indentation, white space + if (options.Indented) + Assert.Equal(26 + options.IndentSize + Environment.NewLine.Length + 1, jsonUtf8.BytesPending); // new lines, indentation, white space else Assert.Equal(26, jsonUtf8.BytesPending); jsonUtf8.Flush(); - if (formatted) - Assert.Equal(26 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted); // new lines, indentation, white space + if (options.Indented) + Assert.Equal(26 + options.IndentSize + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted); // new lines, indentation, white space else Assert.Equal(26, jsonUtf8.BytesCommitted); @@ -3770,50 +3574,41 @@ public void WritePartialHelloWorld(bool formatted, bool skipValidation) jsonUtf8.WriteString("message", "Hello, World!"); jsonUtf8.WriteEndObject(); - if (formatted) - Assert.Equal(26 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted); + if (options.Indented) + Assert.Equal(26 + options.IndentSize + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted); else Assert.Equal(26, jsonUtf8.BytesCommitted); - if (formatted) - Assert.Equal(27 + 2 + (2 * Environment.NewLine.Length) + 1, jsonUtf8.BytesPending); // new lines, indentation, white space + if (options.Indented) + Assert.Equal(27 + options.IndentSize + (2 * Environment.NewLine.Length) + 1, jsonUtf8.BytesPending); // new lines, indentation, white space else Assert.Equal(27, jsonUtf8.BytesPending); jsonUtf8.Flush(); - if (formatted) - Assert.Equal(53 + (2 * 2) + (3 * Environment.NewLine.Length) + (1 * 2), jsonUtf8.BytesCommitted); // new lines, indentation, white space + if (options.Indented) + Assert.Equal(53 + (2 * options.IndentSize) + (3 * Environment.NewLine.Length) + (1 * 2), jsonUtf8.BytesCommitted); // new lines, indentation, white space else Assert.Equal(53, jsonUtf8.BytesCommitted); Assert.Equal(0, jsonUtf8.BytesPending); } + public static IEnumerable WriteBase64String_TestData() => + JsonOptionsWith([ + "message", + "escape mess>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" + ]); + [Theory] - [InlineData(true, true, "message")] - [InlineData(true, false, "message")] - [InlineData(false, true, "message")] - [InlineData(false, false, "message")] - [InlineData(true, true, "escape mess>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - public void WriteBase64String(bool formatted, bool skipValidation, string inputValue) + [MemberData(nameof(WriteBase64String_TestData))] + public void WriteBase64String(JsonWriterOptions options, string inputValue) { string propertyName = inputValue; byte[] value = { 1, 2, 3, 4, 5, 6 }; - string expectedStr = GetBase64ExpectedString(prettyPrint: formatted, propertyName, value); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + string expectedStr = GetBase64ExpectedString(options, propertyName, value); ReadOnlySpan propertyNameSpan = propertyName.AsSpan(); ReadOnlySpan propertyNameSpanUtf8 = Encoding.UTF8.GetBytes(propertyName); @@ -3865,14 +3660,9 @@ public void WriteBase64String(bool formatted, bool skipValidation, string inputV } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WritePartialBase64String(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WritePartialBase64String(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(10); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -3884,15 +3674,15 @@ public void WritePartialBase64String(bool formatted, bool skipValidation) jsonUtf8.WriteBase64String("message", new byte[] { 201, 153, 199 }); Assert.Equal(0, jsonUtf8.BytesCommitted); - if (formatted) - Assert.Equal(17 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesPending); // new lines, indentation, white space + if (options.Indented) + Assert.Equal(17 + options.IndentSize + Environment.NewLine.Length + 1, jsonUtf8.BytesPending); // new lines, indentation, white space else Assert.Equal(17, jsonUtf8.BytesPending); jsonUtf8.Flush(); - if (formatted) - Assert.Equal(17 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted); // new lines, indentation, white space + if (options.Indented) + Assert.Equal(17 + options.IndentSize + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted); // new lines, indentation, white space else Assert.Equal(17, jsonUtf8.BytesCommitted); @@ -3901,20 +3691,20 @@ public void WritePartialBase64String(bool formatted, bool skipValidation) jsonUtf8.WriteBase64String("message", new byte[] { 201, 153, 199 }); jsonUtf8.WriteEndObject(); - if (formatted) - Assert.Equal(17 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted); + if (options.Indented) + Assert.Equal(17 + options.IndentSize + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted); else Assert.Equal(17, jsonUtf8.BytesCommitted); - if (formatted) - Assert.Equal(18 + 2 + (2 * Environment.NewLine.Length) + 1, jsonUtf8.BytesPending); // new lines, indentation, white space + if (options.Indented) + Assert.Equal(18 + options.IndentSize + (2 * Environment.NewLine.Length) + 1, jsonUtf8.BytesPending); // new lines, indentation, white space else Assert.Equal(18, jsonUtf8.BytesPending); jsonUtf8.Flush(); - if (formatted) - Assert.Equal(35 + (2 * 2) + (3 * Environment.NewLine.Length) + (1 * 2), jsonUtf8.BytesCommitted); // new lines, indentation, white space + if (options.Indented) + Assert.Equal(35 + (2 * options.IndentSize) + (3 * Environment.NewLine.Length) + (1 * 2), jsonUtf8.BytesCommitted); // new lines, indentation, white space else Assert.Equal(35, jsonUtf8.BytesCommitted); @@ -3922,13 +3712,9 @@ public void WritePartialBase64String(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteInvalidPartialJson(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteInvalidPartialJson(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new ArrayBufferWriter(10); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -3942,7 +3728,7 @@ public void WriteInvalidPartialJson(bool formatted, bool skipValidation) Assert.Equal(1, jsonUtf8.BytesCommitted); Assert.Equal(0, jsonUtf8.BytesPending); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteStringValue("Hello, World!"); jsonUtf8.WriteEndArray(); @@ -3955,15 +3741,11 @@ public void WriteInvalidPartialJson(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteInvalidBase64(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteInvalidBase64(JsonWriterOptions options) { { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(10); + var output = new ArrayBufferWriter(10); using var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); @@ -3976,7 +3758,7 @@ public void WriteInvalidBase64(bool formatted, bool skipValidation) Assert.Equal(1, jsonUtf8.BytesCommitted); Assert.Equal(0, jsonUtf8.BytesPending); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteBase64StringValue(new byte[] { 1, 2 }); jsonUtf8.WriteEndArray(); @@ -3988,8 +3770,7 @@ public void WriteInvalidBase64(bool formatted, bool skipValidation) } } { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(10); + var output = new ArrayBufferWriter(10); using var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartArray(); @@ -4002,7 +3783,7 @@ public void WriteInvalidBase64(bool formatted, bool skipValidation) Assert.Equal(1, jsonUtf8.BytesCommitted); Assert.Equal(0, jsonUtf8.BytesPending); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteBase64String("foo", new byte[] { 1, 2 }); jsonUtf8.WriteEndObject(); @@ -4060,15 +3841,11 @@ public void WriteBase64DoesNotEscapeLarge() } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteInvalidDepthPartial(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteInvalidDepthPartial(JsonWriterOptions options) { { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(10); + var output = new ArrayBufferWriter(10); using var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); @@ -4077,7 +3854,7 @@ public void WriteInvalidDepthPartial(bool formatted, bool skipValidation) Assert.Equal(0, jsonUtf8.CurrentDepth); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteStartObject(); } @@ -4088,15 +3865,14 @@ public void WriteInvalidDepthPartial(bool formatted, bool skipValidation) } { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(10); + var output = new ArrayBufferWriter(10); using var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - if (skipValidation) + if (options.SkipValidation) { jsonUtf8.WriteStartObject("name"); } @@ -4107,24 +3883,18 @@ public void WriteInvalidDepthPartial(bool formatted, bool skipValidation) } } + public static IEnumerable WriteComments_TestData() => + JsonOptionsWith([ + "comment", + "comm>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" + ]); + [Theory] - [InlineData(true, true, "comment")] - [InlineData(true, false, "comment")] - [InlineData(false, true, "comment")] - [InlineData(false, false, "comment")] - [InlineData(true, true, "comm>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - public void WriteCommentsInArray(bool formatted, bool skipValidation, string comment) - { - string expectedStr = GetCommentInArrayExpectedString(prettyPrint: formatted, comment); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + [MemberData(nameof(WriteComments_TestData))] + public void WriteCommentsInArray(JsonWriterOptions options, string comment) + { + string expectedStr = GetCommentInArrayExpectedString(options, comment); for (int i = 0; i < 3; i++) { @@ -4175,23 +3945,10 @@ public void WriteCommentsInArray(bool formatted, bool skipValidation, string com } [Theory] - [InlineData(true, true, "comment")] - [InlineData(true, false, "comment")] - [InlineData(false, true, "comment")] - [InlineData(false, false, "comment")] - [InlineData(true, true, "comm>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - public void WriteCommentsInObject(bool formatted, bool skipValidation, string comment) - { - string expectedStr = GetCommentInObjectExpectedString(prettyPrint: formatted, comment); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + [MemberData(nameof(WriteComments_TestData))] + public void WriteCommentsInObject(JsonWriterOptions options, string comment) + { + string expectedStr = GetCommentInObjectExpectedString(options, comment); for (int i = 0; i < 3; i++) { @@ -4245,14 +4002,9 @@ private static void WriteCommentValue(Utf8JsonWriter jsonUtf8, int i, string com } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteInvalidComment(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteInvalidComment(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(32); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -4270,14 +4022,9 @@ public void WriteInvalidComment(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteCommentsInvalidTextAllowed(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteCommentsInvalidTextAllowed(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(32); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -4298,18 +4045,18 @@ public void WriteCommentsInvalidTextAllowed(bool formatted, bool skipValidation) jsonUtf8.WriteCommentValue(comment.AsSpan()); jsonUtf8.Flush(); - string expectedStr = GetCommentExpectedString(prettyPrint: formatted); + string expectedStr = GetCommentExpectedString(options); JsonTestHelper.AssertContents(expectedStr, output); } - private static string GetCommentExpectedString(bool prettyPrint) + private static string GetCommentExpectedString(JsonWriterOptions options) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + Formatting = options.Indented ? Formatting.Indented : Formatting.None, StringEscapeHandling = StringEscapeHandling.EscapeHtml, }; @@ -4331,7 +4078,7 @@ private static string GetCommentExpectedString(bool prettyPrint) json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } [Theory] @@ -4421,17 +4168,12 @@ public void WriteCommentsInObject_ComparedWithStringLiteral(bool formatted, stri } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteStrings(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteStrings(JsonWriterOptions options) { string value = "temp"; - string expectedStr = GetStringsExpectedString(prettyPrint: formatted, value); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - + string expectedStr = GetStringsExpectedString(options, value); + for (int i = 0; i < 3; i++) { var output = new ArrayBufferWriter(1024); @@ -4462,32 +4204,21 @@ public void WriteStrings(bool formatted, bool skipValidation) } } + public static IEnumerable WriteHelloWorldEscaped_AdditionalCases_TestData() => + JsonOptionsWith([ + "mess\nage", + "message" + ], + [ + "Hello, \nWorld!", + "Hello, World!" + ]); + [Theory] - [InlineData(true, true, "mess\nage", "Hello, \nWorld!")] - [InlineData(true, false, "mess\nage", "Hello, \nWorld!")] - [InlineData(false, true, "mess\nage", "Hello, \nWorld!")] - [InlineData(false, false, "mess\nage", "Hello, \nWorld!")] - [InlineData(true, true, "message", "Hello, \nWorld!")] - [InlineData(true, false, "message", "Hello, \nWorld!")] - [InlineData(false, true, "message", "Hello, \nWorld!")] - [InlineData(false, false, "message", "Hello, \nWorld!")] - [InlineData(true, true, "mess\nage", "Hello, World!")] - [InlineData(true, false, "mess\nage", "Hello, World!")] - [InlineData(false, true, "mess\nage", "Hello, World!")] - [InlineData(false, false, "mess\nage", "Hello, World!")] - [InlineData(true, true, "message", "Hello, World!")] - [InlineData(true, false, "message", "Hello, World!")] - [InlineData(false, true, "message", "Hello, World!")] - [InlineData(false, false, "message", "Hello, World!")] - [InlineData(true, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>mess\nage", ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Hello, \nWorld!")] - [InlineData(true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>mess\nage", ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Hello, \nWorld!")] - [InlineData(false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>mess\nage", ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Hello, \nWorld!")] - [InlineData(false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>mess\nage", ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Hello, \nWorld!")] - public void WriteHelloWorldEscaped_AdditionalCases(bool formatted, bool skipValidation, string key, string value) - { - string expectedStr = GetEscapedExpectedString(prettyPrint: formatted, key, value, StringEscapeHandling.EscapeHtml); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + [MemberData(nameof(WriteHelloWorldEscaped_AdditionalCases_TestData))] + public void WriteHelloWorldEscaped_AdditionalCases(JsonWriterOptions options, string key, string value) + { + string expectedStr = GetEscapedExpectedString(options, key, value, StringEscapeHandling.EscapeHtml); byte[] keyUtf8 = Encoding.UTF8.GetBytes(key); byte[] valueUtf8 = Encoding.UTF8.GetBytes(value); @@ -4626,11 +4357,8 @@ public void WriteHelloWorldEscaped_AdditionalCases(bool formatted, bool skipVali } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void EscapeAsciiCharacters(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void EscapeAsciiCharacters(JsonWriterOptions options) { var propertyArray = new char[128]; @@ -4650,9 +4378,8 @@ public void EscapeAsciiCharacters(bool formatted, bool skipValidation) string propertyName = new string(propertyArray); string value = new string(propertyArray); - string expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeHtml); + string expectedStr = GetEscapedExpectedString(options, propertyName, value, StringEscapeHandling.EscapeHtml); - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; for (int i = 0; i < 6; i++) { var output = new ArrayBufferWriter(1024); @@ -4693,11 +4420,8 @@ public void EscapeAsciiCharacters(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void EscapeCharacters(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void EscapeCharacters(JsonWriterOptions options) { // Do not include surrogate pairs. var propertyArray = new char[0xD800 + (0xFFFF - 0xE000) + 1]; @@ -4713,9 +4437,8 @@ public void EscapeCharacters(bool formatted, bool skipValidation) string propertyName = new string(propertyArray); string value = new string(propertyArray); - string expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii); + string expectedStr = GetEscapedExpectedString(options, propertyName, value, StringEscapeHandling.EscapeNonAscii); - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; for (int i = 0; i < 6; i++) { var output = new ArrayBufferWriter(1024); @@ -4756,20 +4479,16 @@ public void EscapeCharacters(bool formatted, bool skipValidation) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void HighSurrogateMissingGetsReplaced(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void HighSurrogateMissingGetsReplaced(JsonWriterOptions options) { var propertyArray = new char[10] { 'a', (char)0xD800, (char)0xDC00, (char)0xD803, (char)0xDE6D, (char)0xD834, (char)0xDD1E, (char)0xDBFF, (char)0xDFFF, 'a' }; string propertyName = new string(propertyArray); string value = new string(propertyArray); - string expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii); + string expectedStr = GetEscapedExpectedString(options, propertyName, value, StringEscapeHandling.EscapeNonAscii); - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; for (int i = 0; i < 6; i++) { var output = new ArrayBufferWriter(1024); @@ -5074,13 +4793,9 @@ public void CustomEscaper(string value, string expectedStr) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteCustomStrings(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteCustomStrings(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new ArrayBufferWriter(10); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -5094,19 +4809,15 @@ public void WriteCustomStrings(bool formatted, bool skipValidation) jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - JsonTestHelper.AssertContents(GetCustomExpectedString(formatted), output); + JsonTestHelper.AssertContents(GetCustomExpectedString(options), output); } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteStartEnd(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteStartEnd(JsonWriterOptions options) { - string expectedStr = GetStartEndExpectedString(prettyPrint: formatted); + string expectedStr = GetStartEndExpectedString(options); - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new ArrayBufferWriter(1024); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -5157,16 +4868,12 @@ public void WriteStartEndInvalid(bool formatted) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteStartEndWithPropertyNameArray(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteStartEndWithPropertyNameArray(JsonWriterOptions options) { - string expectedStr = GetStartEndWithPropertyArrayExpectedString(prettyPrint: formatted); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + string expectedStr = GetStartEndWithPropertyArrayExpectedString(options); + for (int i = 0; i < 3; i++) { var output = new ArrayBufferWriter(1024); @@ -5195,16 +4902,15 @@ public void WriteStartEndWithPropertyNameArray(bool formatted, bool skipValidati } } + public static IEnumerable WriteStartEndWithPropertyName_TestData() => + JsonOptionsWith([ + 10, + 100 + ]); + [Theory] - [InlineData(true, true, 10)] - [InlineData(true, false, 10)] - [InlineData(false, true, 10)] - [InlineData(false, false, 10)] - [InlineData(true, true, 100)] - [InlineData(true, false, 100)] - [InlineData(false, true, 100)] - [InlineData(false, false, 100)] - public void WriteStartEndWithPropertyNameArrayDifferentKeyLengths(bool formatted, bool skipValidation, int keyLength) + [MemberData(nameof(WriteStartEndWithPropertyName_TestData))] + public void WriteStartEndWithPropertyNameArrayDifferentKeyLengths(JsonWriterOptions options, int keyLength) { var keyChars = new char[keyLength]; for (int i = 0; i < keyChars.Length; i++) @@ -5213,9 +4919,7 @@ public void WriteStartEndWithPropertyNameArrayDifferentKeyLengths(bool formatted } var key = new string(keyChars); - string expectedStr = GetStartEndWithPropertyArrayExpectedString(key, prettyPrint: formatted, escape: true); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + string expectedStr = GetStartEndWithPropertyArrayExpectedString(key, options, escape: true); for (int i = 0; i < 3; i++) { @@ -5246,15 +4950,10 @@ public void WriteStartEndWithPropertyNameArrayDifferentKeyLengths(bool formatted } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteStartEndWithPropertyNameObject(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteStartEndWithPropertyNameObject(JsonWriterOptions options) { - string expectedStr = GetStartEndWithPropertyObjectExpectedString(prettyPrint: formatted); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + string expectedStr = GetStartEndWithPropertyObjectExpectedString(options); for (int i = 0; i < 3; i++) { @@ -5285,15 +4984,8 @@ public void WriteStartEndWithPropertyNameObject(bool formatted, bool skipValidat } [Theory] - [InlineData(true, true, 10)] - [InlineData(true, false, 10)] - [InlineData(false, true, 10)] - [InlineData(false, false, 10)] - [InlineData(true, true, 100)] - [InlineData(true, false, 100)] - [InlineData(false, true, 100)] - [InlineData(false, false, 100)] - public void WriteStartEndWithPropertyNameObjectDifferentKeyLengths(bool formatted, bool skipValidation, int keyLength) + [MemberData(nameof(WriteStartEndWithPropertyName_TestData))] + public void WriteStartEndWithPropertyNameObjectDifferentKeyLengths(JsonWriterOptions options, int keyLength) { var keyChars = new char[keyLength]; for (int i = 0; i < keyChars.Length; i++) @@ -5302,9 +4994,7 @@ public void WriteStartEndWithPropertyNameObjectDifferentKeyLengths(bool formatte } var key = new string(keyChars); - string expectedStr = GetStartEndWithPropertyObjectExpectedString(key, prettyPrint: formatted, escape: true); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + string expectedStr = GetStartEndWithPropertyObjectExpectedString(key, options, escape: true); for (int i = 0; i < 3; i++) { @@ -5335,15 +5025,10 @@ public void WriteStartEndWithPropertyNameObjectDifferentKeyLengths(bool formatte } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteArrayWithProperty(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteArrayWithProperty(JsonWriterOptions options) { - string expectedStr = GetArrayWithPropertyExpectedString(prettyPrint: formatted); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + string expectedStr = GetArrayWithPropertyExpectedString(options); for (int i = 0; i < 3; i++) { @@ -5373,36 +5058,22 @@ public void WriteArrayWithProperty(bool formatted, bool skipValidation) } } + public static IEnumerable WriteBooleanValue_TestData() => + JsonOptionsWith([ + true, + false + ], + [ + "message", + "mess>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" + ]); + [Theory] - [InlineData(true, true, true, "message")] - [InlineData(true, false, true, "message")] - [InlineData(false, true, true, "message")] - [InlineData(false, false, true, "message")] - [InlineData(true, true, true, "mess>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(true, false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, true, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(true, true, false, "message")] - [InlineData(true, false, false, "message")] - [InlineData(false, true, false, "message")] - [InlineData(false, false, false, "message")] - [InlineData(true, true, false, "mess>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(true, false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - public void WriteBooleanValue(bool formatted, bool skipValidation, bool value, string keyString) - { - string expectedStr = GetBooleanExpectedString(prettyPrint: formatted, keyString, value, escape: true); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + [MemberData(nameof(WriteBooleanValue_TestData))] + public void WriteBooleanValue(JsonWriterOptions options, bool value, string keyString) + { + string expectedStr = GetBooleanExpectedString(options, keyString, value, escape: true); for (int i = 0; i < 4; i++) { @@ -5442,24 +5113,18 @@ public void WriteBooleanValue(bool formatted, bool skipValidation, bool value, s } } + public static IEnumerable WriteValue_TestData() => + JsonOptionsWith([ + "message", + "mess>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" + ]); + [Theory] - [InlineData(true, true, "message")] - [InlineData(true, false, "message")] - [InlineData(false, true, "message")] - [InlineData(false, false, "message")] - [InlineData(true, true, "mess>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - public void WriteNullValue(bool formatted, bool skipValidation, string keyString) - { - string expectedStr = GetNullExpectedString(prettyPrint: formatted, keyString, escape: true); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + [MemberData(nameof(WriteValue_TestData))] + public void WriteNullValue(JsonWriterOptions options, string keyString) + { + string expectedStr = GetNullExpectedString(options, keyString, escape: true); for (int i = 0; i < 4; i++) { @@ -5502,36 +5167,21 @@ public void WriteNullValue(bool formatted, bool skipValidation, string keyString } } + public static IEnumerable WriteIntegerValue_TestData() => + JsonOptionsWith([ + 0, + -1, + 1, + int.MaxValue, + int.MinValue, + 12345 + ]); + [Theory] - [InlineData(true, true, 0)] - [InlineData(true, false, 0)] - [InlineData(false, true, 0)] - [InlineData(false, false, 0)] - [InlineData(true, true, -1)] - [InlineData(true, false, -1)] - [InlineData(false, true, -1)] - [InlineData(false, false, -1)] - [InlineData(true, true, 1)] - [InlineData(true, false, 1)] - [InlineData(false, true, 1)] - [InlineData(false, false, 1)] - [InlineData(true, true, int.MaxValue)] - [InlineData(true, false, int.MaxValue)] - [InlineData(false, true, int.MaxValue)] - [InlineData(false, false, int.MaxValue)] - [InlineData(true, true, int.MinValue)] - [InlineData(true, false, int.MinValue)] - [InlineData(false, true, int.MinValue)] - [InlineData(false, false, int.MinValue)] - [InlineData(true, true, 12345)] - [InlineData(true, false, 12345)] - [InlineData(false, true, 12345)] - [InlineData(false, false, 12345)] - public void WriteIntegerValue(bool formatted, bool skipValidation, int value) - { - string expectedStr = GetPropertyExpectedString(prettyPrint: formatted, value); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + [MemberData(nameof(WriteIntegerValue_TestData))] + public void WriteIntegerValue(JsonWriterOptions options, int value) + { + string expectedStr = GetPropertyExpectedString(options, value); for (int i = 0; i < 4; i++) { @@ -5564,20 +5214,17 @@ public void WriteIntegerValue(bool formatted, bool skipValidation, int value) } } + public static IEnumerable WriteFloatValue_TestData() => + JsonOptionsWith([ + float.MinValue, + float.MaxValue + ]); + [Theory] - [InlineData(true, true, float.MinValue)] - [InlineData(true, false, float.MinValue)] - [InlineData(false, true, float.MinValue)] - [InlineData(false, false, float.MinValue)] - [InlineData(true, true, float.MaxValue)] - [InlineData(true, false, float.MaxValue)] - [InlineData(false, true, float.MaxValue)] - [InlineData(false, false, float.MaxValue)] - public void WriteFloatValue(bool formatted, bool skipValidation, float value) + [MemberData(nameof(WriteFloatValue_TestData))] + public void WriteFloatValue(JsonWriterOptions options, float value) { - string expectedStr = GetPropertyExpectedString(prettyPrint: formatted, value); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + string expectedStr = GetPropertyExpectedString(options, value); for (int i = 0; i < 4; i++) { @@ -5610,20 +5257,17 @@ public void WriteFloatValue(bool formatted, bool skipValidation, float value) } } + public static IEnumerable WriteDoubleValue_TestData() => + JsonOptionsWith([ + double.MinValue, + double.MaxValue + ]); + [Theory] - [InlineData(true, true, double.MinValue)] - [InlineData(true, false, double.MinValue)] - [InlineData(false, true, double.MinValue)] - [InlineData(false, false, double.MinValue)] - [InlineData(true, true, double.MaxValue)] - [InlineData(true, false, double.MaxValue)] - [InlineData(false, true, double.MaxValue)] - [InlineData(false, false, double.MaxValue)] - public void WriteDoubleValue(bool formatted, bool skipValidation, double value) + [MemberData(nameof(WriteDoubleValue_TestData))] + public void WriteDoubleValue(JsonWriterOptions options, double value) { - string expectedStr = GetPropertyExpectedString(prettyPrint: formatted, value); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + string expectedStr = GetPropertyExpectedString(options, value); for (int i = 0; i < 4; i++) { @@ -5658,19 +5302,8 @@ public void WriteDoubleValue(bool formatted, bool skipValidation, double value) [Theory] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] - [InlineData(true, true, "message")] - [InlineData(true, false, "message")] - [InlineData(false, true, "message")] - [InlineData(false, false, "message")] - [InlineData(true, true, "mess>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - public void WriteNumbers(bool formatted, bool skipValidation, string keyString) + [MemberData(nameof(WriteValue_TestData))] + public void WriteNumbers(JsonWriterOptions options, string keyString) { var random = new Random(42); const int numberOfItems = 1_000; @@ -5798,9 +5431,7 @@ public void WriteNumbers(bool formatted, bool skipValidation, string keyString) } } - string expectedStr = GetNumbersExpectedString(prettyPrint: formatted, keyString, ints, uints, longs, ulongs, floats, doubles, decimals, escape: false); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + string expectedStr = GetNumbersExpectedString(options, keyString, ints, uints, longs, ulongs, floats, doubles, decimals, escape: false); for (int j = 0; j < 3; j++) { @@ -5884,14 +5515,9 @@ public void WriteNumbers(bool formatted, bool skipValidation, string keyString) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteNumberValueInt32(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteNumberValueInt32(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -5908,19 +5534,14 @@ public void WriteNumberValueInt32(bool formatted, bool skipValidation) jsonUtf8.WriteEndArray(); jsonUtf8.Flush(); - string expectedStr = GetNumbersExpectedString(formatted, numberOfElements, value); + string expectedStr = GetNumbersExpectedString(options, numberOfElements, value); JsonTestHelper.AssertContents(expectedStr, output); } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteNumberValueInt64(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteNumberValueInt64(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -5937,19 +5558,14 @@ public void WriteNumberValueInt64(bool formatted, bool skipValidation) jsonUtf8.WriteEndArray(); jsonUtf8.Flush(); - string expectedStr = GetNumbersExpectedString(formatted, numberOfElements, value); + string expectedStr = GetNumbersExpectedString(options, numberOfElements, value); JsonTestHelper.AssertContents(expectedStr, output); } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteNumberValueUInt32(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteNumberValueUInt32(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -5966,19 +5582,14 @@ public void WriteNumberValueUInt32(bool formatted, bool skipValidation) jsonUtf8.WriteEndArray(); jsonUtf8.Flush(); - string expectedStr = GetNumbersExpectedString(formatted, numberOfElements, value); + string expectedStr = GetNumbersExpectedString(options, numberOfElements, value); JsonTestHelper.AssertContents(expectedStr, output); } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteNumberValueUInt64(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteNumberValueUInt64(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -5995,23 +5606,14 @@ public void WriteNumberValueUInt64(bool formatted, bool skipValidation) jsonUtf8.WriteEndArray(); jsonUtf8.Flush(); - string expectedStr = GetNumbersExpectedString(formatted, numberOfElements, value); + string expectedStr = GetNumbersExpectedString(options, numberOfElements, value); JsonTestHelper.AssertContents(expectedStr, output); } [Theory] - [InlineData(true, true, float.MinValue)] - [InlineData(true, false, float.MinValue)] - [InlineData(false, true, float.MinValue)] - [InlineData(false, false, float.MinValue)] - [InlineData(true, true, float.MaxValue)] - [InlineData(true, false, float.MaxValue)] - [InlineData(false, true, float.MaxValue)] - [InlineData(false, false, float.MaxValue)] - public void WriteNumberValueSingle(bool formatted, bool skipValidation, float value) - { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - + [MemberData(nameof(WriteFloatValue_TestData))] + public void WriteNumberValueSingle(JsonWriterOptions options, float value) + { var output = new ArrayBufferWriter(); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -6027,23 +5629,14 @@ public void WriteNumberValueSingle(bool formatted, bool skipValidation, float va jsonUtf8.WriteEndArray(); jsonUtf8.Flush(); - string expectedStr = GetNumbersExpectedString(formatted, numberOfElements, value); + string expectedStr = GetNumbersExpectedString(options, numberOfElements, value); JsonTestHelper.AssertContents(expectedStr, output); } [Theory] - [InlineData(true, true, double.MinValue)] - [InlineData(true, false, double.MinValue)] - [InlineData(false, true, double.MinValue)] - [InlineData(false, false, double.MinValue)] - [InlineData(true, true, double.MaxValue)] - [InlineData(true, false, double.MaxValue)] - [InlineData(false, true, double.MaxValue)] - [InlineData(false, false, double.MaxValue)] - public void WriteNumberValueDouble(bool formatted, bool skipValidation, double value) - { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - + [MemberData(nameof(WriteDoubleValue_TestData))] + public void WriteNumberValueDouble(JsonWriterOptions options, double value) + { var output = new ArrayBufferWriter(); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -6059,19 +5652,14 @@ public void WriteNumberValueDouble(bool formatted, bool skipValidation, double v jsonUtf8.WriteEndArray(); jsonUtf8.Flush(); - string expectedStr = GetNumbersExpectedString(formatted, numberOfElements, value); + string expectedStr = GetNumbersExpectedString(options, numberOfElements, value); JsonTestHelper.AssertContents(expectedStr, output); } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteNumberValueDecimal(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteNumberValueDecimal(JsonWriterOptions options) { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -6088,7 +5676,7 @@ public void WriteNumberValueDecimal(bool formatted, bool skipValidation) jsonUtf8.WriteEndArray(); jsonUtf8.Flush(); - string expectedStr = GetNumbersExpectedString(formatted, numberOfElements, value); + string expectedStr = GetNumbersExpectedString(options, numberOfElements, value); JsonTestHelper.AssertContents(expectedStr, output); } @@ -6168,19 +5756,8 @@ private static void WriteStringHelper(JsonWriterOptions options, string keyStrin } [Theory] - [InlineData(true, true, "message")] - [InlineData(true, false, "message")] - [InlineData(false, true, "message")] - [InlineData(false, false, "message")] - [InlineData(true, true, "mess>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - public void WriteGuidsValue(bool formatted, bool skipValidation, string keyString) + [MemberData(nameof(WriteValue_TestData))] + public void WriteGuidsValue(JsonWriterOptions options, string keyString) { const int numberOfItems = 1_000; @@ -6190,9 +5767,7 @@ public void WriteGuidsValue(bool formatted, bool skipValidation, string keyStrin guids[i] = Guid.NewGuid(); } - string expectedStr = GetGuidsExpectedString(prettyPrint: formatted, keyString, guids, escape: true); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + string expectedStr = GetGuidsExpectedString(options, keyString, guids, escape: true); ReadOnlySpan keyUtf16 = keyString.AsSpan(); ReadOnlySpan keyUtf8 = Encoding.UTF8.GetBytes(keyString); @@ -6235,19 +5810,8 @@ public void WriteGuidsValue(bool formatted, bool skipValidation, string keyStrin } [Theory] - [InlineData(true, true, "message")] - [InlineData(true, false, "message")] - [InlineData(false, true, "message")] - [InlineData(false, false, "message")] - [InlineData(true, true, "mess>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - public void WriteDateTimesValue(bool formatted, bool skipValidation, string keyString) + [MemberData(nameof(WriteValue_TestData))] + public void WriteDateTimesValue(JsonWriterOptions options, string keyString) { var random = new Random(42); const int numberOfItems = 1_000; @@ -6259,9 +5823,7 @@ public void WriteDateTimesValue(bool formatted, bool skipValidation, string keyS for (int i = 0; i < numberOfItems; i++) dates[i] = start.AddDays(random.Next(range)); - string expectedStr = GetDatesExpectedString(prettyPrint: formatted, keyString, dates, escape: true); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + string expectedStr = GetDatesExpectedString(options, keyString, dates, escape: true); ReadOnlySpan keyUtf16 = keyString.AsSpan(); ReadOnlySpan keyUtf8 = Encoding.UTF8.GetBytes(keyString); @@ -6304,19 +5866,8 @@ public void WriteDateTimesValue(bool formatted, bool skipValidation, string keyS } [Theory] - [InlineData(true, true, "message")] - [InlineData(true, false, "message")] - [InlineData(false, true, "message")] - [InlineData(false, false, "message")] - [InlineData(true, true, "mess>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(true, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, true, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - [InlineData(false, false, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")] - public void WriteDateTimeOffsetsValue(bool formatted, bool skipValidation, string keyString) + [MemberData(nameof(WriteValue_TestData))] + public void WriteDateTimeOffsetsValue(JsonWriterOptions options, string keyString) { var random = new Random(42); const int numberOfItems = 1_000; @@ -6328,9 +5879,7 @@ public void WriteDateTimeOffsetsValue(bool formatted, bool skipValidation, strin for (int i = 0; i < numberOfItems; i++) dates[i] = new DateTimeOffset(start.AddDays(random.Next(range))); - string expectedStr = GetDatesExpectedString(prettyPrint: formatted, keyString, dates, escape: true); - - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + string expectedStr = GetDatesExpectedString(options, keyString, dates, escape: true); ReadOnlySpan keyUtf16 = keyString.AsSpan(); ReadOnlySpan keyUtf8 = Encoding.UTF8.GetBytes(keyString); @@ -6379,11 +5928,8 @@ public void WriteDateTimeOffsetsValue(bool formatted, bool skipValidation, strin [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.OSX)] [ConditionalTheory(typeof(Environment), nameof(Environment.Is64BitProcess))] [OuterLoop] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteLargeKeyOrValue(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteLargeKeyOrValue(JsonWriterOptions options) { try { @@ -6396,8 +5942,6 @@ public void WriteLargeKeyOrValue(bool formatted, bool skipValidation) key.AsSpan().Fill((byte)'a'); value.AsSpan().Fill((byte)'b'); - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - { var output = new ArrayBufferWriter(1024); using var jsonUtf8 = new Utf8JsonWriter(output, options); @@ -6427,16 +5971,12 @@ public void WriteLargeKeyOrValue(bool formatted, bool skipValidation) [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.OSX)] [ConditionalTheory(typeof(Environment), nameof(Environment.Is64BitProcess))] [OuterLoop] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteLargeKeyValue(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteLargeKeyValue(JsonWriterOptions options) { try { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - + Span key; Span value; @@ -6461,16 +6001,12 @@ public void WriteLargeKeyValue(bool formatted, bool skipValidation) [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.OSX)] [ConditionalTheory(typeof(Environment), nameof(Environment.Is64BitProcess))] [OuterLoop] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WriteLargeKeyEscapedValue(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteLargeKeyEscapedValue(JsonWriterOptions options) { try { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - + Span key; Span value; @@ -6855,16 +6391,11 @@ private static void WriteStringHelper(JsonEncodedText text, string expectedMessa [ConditionalTheory(typeof(Environment), nameof(Environment.Is64BitProcess))] [OuterLoop] - [InlineData(true, true)] - [InlineData(false, true)] - [InlineData(true, false)] - [InlineData(false, false)] - public void WriteTooLargeArguments(bool formatted, bool skipValidation) + [MemberData(nameof(JsonOptions_TestData))] + public void WriteTooLargeArguments(JsonWriterOptions options) { try { - var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - byte[] bytesTooLarge; char[] charsTooLarge; var bytes = new byte[5]; @@ -7252,6 +6783,20 @@ public static void WriteStringValue_JsonEncodedTextProperty_NullString() (writer, value) => writer.WriteString(jet, value)); } + [Fact] + public static void WriteStringValue_IndentationOptions() + { + var options = new JsonWriterOptions(); + var expectedOutput = GetCustomExpectedString(options); + + options.IndentCharacter = '\t'; + options.IndentSize = 127; + + var output = GetCustomExpectedString(options); + + Assert.Equal(expectedOutput, output); + } + private delegate void WriteValueSpanAction( Utf8JsonWriter writer, ReadOnlySpan value); @@ -7405,14 +6950,14 @@ private static void WriteNullValue_InArray( JsonTestHelper.AssertContents($"[{nullValue},{wireValue},{wireValue}]", output); } - private static string GetHelloWorldExpectedString(bool prettyPrint, string propertyName, string value) + private static string GetHelloWorldExpectedString(JsonWriterOptions options, string propertyName, string value) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + Formatting = options.Indented ? Formatting.Indented : Formatting.None, StringEscapeHandling = StringEscapeHandling.EscapeHtml }; @@ -7425,17 +6970,17 @@ private static string GetHelloWorldExpectedString(bool prettyPrint, string prope json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } - private static string GetBase64ExpectedString(bool prettyPrint, string propertyName, byte[] value) + private static string GetBase64ExpectedString(JsonWriterOptions options, string propertyName, byte[] value) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + Formatting = options.Indented ? Formatting.Indented : Formatting.None, StringEscapeHandling = StringEscapeHandling.EscapeHtml }; @@ -7453,23 +6998,23 @@ private static string GetBase64ExpectedString(bool prettyPrint, string propertyN json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } - private static string GetCommentInArrayExpectedString(bool prettyPrint, string comment) + private static string GetCommentInArrayExpectedString(JsonWriterOptions options, string comment) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + Formatting = options.Indented ? Formatting.Indented : Formatting.None, StringEscapeHandling = StringEscapeHandling.EscapeHtml, }; json.WriteComment(comment); - CompensateNewLine(prettyPrint, json, streamWriter); + CompensateNewLine(options.Indented, json, streamWriter); json.WriteStartArray(); for (int j = 0; j < 10; j++) json.WriteComment(comment); @@ -7483,87 +7028,87 @@ private static string GetCommentInArrayExpectedString(bool prettyPrint, string c json.WriteComment(comment); json.WriteEnd(); - CompensateNewLine(prettyPrint, json, streamWriter); + CompensateNewLine(options.Indented, json, streamWriter); json.WriteComment(comment); json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } - private static string GetCommentInObjectExpectedString(bool prettyPrint, string comment) + private static string GetCommentInObjectExpectedString(JsonWriterOptions options, string comment) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + Formatting = options.Indented ? Formatting.Indented : Formatting.None, StringEscapeHandling = StringEscapeHandling.EscapeHtml, }; json.WriteComment(comment); - CompensateNewLine(prettyPrint, json, streamWriter); + CompensateNewLine(options.Indented, json, streamWriter); json.WriteStartObject(); - CompensateNewLine(prettyPrint, json, streamWriter); - CompensateWhitespaces(prettyPrint, json, streamWriter, 2); + CompensateNewLine(options.Indented, json, streamWriter); + CompensateWhitespaces(options.Indented, json, streamWriter, 2); json.WriteComment(comment); json.WritePropertyName("property1"); - CompensateWhitespaces(prettyPrint, json, streamWriter); - CompensateNewLine(prettyPrint, json, streamWriter); - CompensateWhitespaces(prettyPrint, json, streamWriter); + CompensateWhitespaces(options.Indented, json, streamWriter); + CompensateNewLine(options.Indented, json, streamWriter); + CompensateWhitespaces(options.Indented, json, streamWriter); json.WriteComment(comment); - CompensateNewLine(prettyPrint, json, streamWriter); - CompensateWhitespaces(prettyPrint, json, streamWriter); + CompensateNewLine(options.Indented, json, streamWriter); + CompensateWhitespaces(options.Indented, json, streamWriter); json.WriteStartArray(); json.WriteEndArray(); - CompensateNewLine(prettyPrint, json, streamWriter); - CompensateWhitespaces(prettyPrint, json, streamWriter, 2); + CompensateNewLine(options.Indented, json, streamWriter); + CompensateWhitespaces(options.Indented, json, streamWriter, 2); json.WriteComment(comment); json.WritePropertyName("property2"); - CompensateWhitespaces(prettyPrint, json, streamWriter); - CompensateNewLine(prettyPrint, json, streamWriter); - CompensateWhitespaces(prettyPrint, json, streamWriter); + CompensateWhitespaces(options.Indented, json, streamWriter); + CompensateNewLine(options.Indented, json, streamWriter); + CompensateWhitespaces(options.Indented, json, streamWriter); json.WriteComment(comment); - CompensateNewLine(prettyPrint, json, streamWriter); - CompensateWhitespaces(prettyPrint, json, streamWriter); + CompensateNewLine(options.Indented, json, streamWriter); + CompensateWhitespaces(options.Indented, json, streamWriter); json.WriteStartObject(); json.WriteEndObject(); - CompensateNewLine(prettyPrint, json, streamWriter); - CompensateWhitespaces(prettyPrint, json, streamWriter, 2); + CompensateNewLine(options.Indented, json, streamWriter); + CompensateWhitespaces(options.Indented, json, streamWriter, 2); json.WriteComment(comment); json.WriteEnd(); - CompensateNewLine(prettyPrint, json, streamWriter); + CompensateNewLine(options.Indented, json, streamWriter); json.WriteComment(comment); json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } - private static string GetStringsExpectedString(bool prettyPrint, string value) + private static string GetStringsExpectedString(JsonWriterOptions options, string value) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None + Formatting = options.Indented ? Formatting.Indented : Formatting.None }; json.WriteStartArray(); @@ -7573,15 +7118,15 @@ private static string GetStringsExpectedString(bool prettyPrint, string value) json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } - private static string GetEscapedExpectedString(bool prettyPrint, string propertyName, string value, StringEscapeHandling escaping, bool escape = true) + private static string GetEscapedExpectedString(JsonWriterOptions options, string propertyName, string value, StringEscapeHandling escaping, bool escape = true) { using (TextWriter stringWriter = new StringWriter()) using (var json = new JsonTextWriter(stringWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + Formatting = options.Indented ? Formatting.Indented : Formatting.None, StringEscapeHandling = escaping }) { @@ -7591,18 +7136,18 @@ private static string GetEscapedExpectedString(bool prettyPrint, string property json.WriteEnd(); json.Flush(); - return stringWriter.ToString(); + return HandleIndent(stringWriter.ToString(), options); } } - private static string GetCustomExpectedString(bool prettyPrint) + private static string GetCustomExpectedString(JsonWriterOptions options) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None + Formatting = options.Indented ? Formatting.Indented : Formatting.None }; json.WriteStartObject(); @@ -7615,17 +7160,17 @@ private static string GetCustomExpectedString(bool prettyPrint) json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } - private static string GetStartEndExpectedString(bool prettyPrint) + private static string GetStartEndExpectedString(JsonWriterOptions options) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None + Formatting = options.Indented ? Formatting.Indented : Formatting.None }; json.WriteStartArray(); @@ -7635,17 +7180,17 @@ private static string GetStartEndExpectedString(bool prettyPrint) json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } - private static string GetStartEndWithPropertyArrayExpectedString(bool prettyPrint) + private static string GetStartEndWithPropertyArrayExpectedString(JsonWriterOptions options) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None + Formatting = options.Indented ? Formatting.Indented : Formatting.None }; json.WriteStartObject(); @@ -7656,17 +7201,17 @@ private static string GetStartEndWithPropertyArrayExpectedString(bool prettyPrin json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } - private static string GetStartEndWithPropertyArrayExpectedString(string key, bool prettyPrint, bool escape = false) + private static string GetStartEndWithPropertyArrayExpectedString(string key, JsonWriterOptions options, bool escape = false) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + Formatting = options.Indented ? Formatting.Indented : Formatting.None, StringEscapeHandling = StringEscapeHandling.EscapeHtml }; @@ -7678,17 +7223,17 @@ private static string GetStartEndWithPropertyArrayExpectedString(string key, boo json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } - private static string GetStartEndWithPropertyObjectExpectedString(bool prettyPrint) + private static string GetStartEndWithPropertyObjectExpectedString(JsonWriterOptions options) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None + Formatting = options.Indented ? Formatting.Indented : Formatting.None }; json.WriteStartObject(); @@ -7699,17 +7244,17 @@ private static string GetStartEndWithPropertyObjectExpectedString(bool prettyPri json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } - private static string GetStartEndWithPropertyObjectExpectedString(string key, bool prettyPrint, bool escape = false) + private static string GetStartEndWithPropertyObjectExpectedString(string key, JsonWriterOptions options, bool escape = false) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + Formatting = options.Indented ? Formatting.Indented : Formatting.None, StringEscapeHandling = StringEscapeHandling.EscapeHtml }; @@ -7721,17 +7266,17 @@ private static string GetStartEndWithPropertyObjectExpectedString(string key, bo json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } - private static string GetArrayWithPropertyExpectedString(bool prettyPrint) + private static string GetArrayWithPropertyExpectedString(JsonWriterOptions options) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None + Formatting = options.Indented ? Formatting.Indented : Formatting.None }; json.WriteStartObject(); @@ -7741,17 +7286,17 @@ private static string GetArrayWithPropertyExpectedString(bool prettyPrint) json.WriteEndObject(); json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } - private static string GetBooleanExpectedString(bool prettyPrint, string keyString, bool value, bool escape = false) + private static string GetBooleanExpectedString(JsonWriterOptions options, string keyString, bool value, bool escape = false) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + Formatting = options.Indented ? Formatting.Indented : Formatting.None, StringEscapeHandling = StringEscapeHandling.EscapeHtml, }; @@ -7771,17 +7316,17 @@ private static string GetBooleanExpectedString(bool prettyPrint, string keyStrin json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } - private static string GetNullExpectedString(bool prettyPrint, string keyString, bool escape = false) + private static string GetNullExpectedString(JsonWriterOptions options, string keyString, bool escape = false) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + Formatting = options.Indented ? Formatting.Indented : Formatting.None, StringEscapeHandling = StringEscapeHandling.EscapeHtml, }; @@ -7801,17 +7346,17 @@ private static string GetNullExpectedString(bool prettyPrint, string keyString, json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } - private static string GetPropertyExpectedString(bool prettyPrint, T value) + private static string GetPropertyExpectedString(JsonWriterOptions options, T value) { var sb = new StringBuilder(); StringWriter stringWriter = new StringWriter(sb); var json = new JsonTextWriter(stringWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None + Formatting = options.Indented ? Formatting.Indented : Formatting.None }; json.WriteStartObject(); @@ -7821,17 +7366,17 @@ private static string GetPropertyExpectedString(bool prettyPrint, T value) json.Flush(); - return sb.ToString(); + return HandleIndent(sb.ToString(), options); } - private static string GetNumbersExpectedString(bool prettyPrint, string keyString, int[] ints, uint[] uints, long[] longs, ulong[] ulongs, float[] floats, double[] doubles, decimal[] decimals, bool escape = false) + private static string GetNumbersExpectedString(JsonWriterOptions options, string keyString, int[] ints, uint[] uints, long[] longs, ulong[] ulongs, float[] floats, double[] doubles, decimal[] decimals, bool escape = false) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None + Formatting = options.Indented ? Formatting.Indented : Formatting.None }; json.WriteStartObject(); @@ -7887,7 +7432,7 @@ private static string GetNumbersExpectedString(bool prettyPrint, string keyStrin json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetExpectedString_RelaxedEscaping(bool prettyPrint, string keyString) @@ -7918,14 +7463,14 @@ private static string GetExpectedString_RelaxedEscaping(bool prettyPrint, string return Encoding.UTF8.GetString(ms.ToArray()); } - private static string GetGuidsExpectedString(bool prettyPrint, string keyString, Guid[] guids, bool escape = false) + private static string GetGuidsExpectedString(JsonWriterOptions options, string keyString, Guid[] guids, bool escape = false) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + Formatting = options.Indented ? Formatting.Indented : Formatting.None, StringEscapeHandling = StringEscapeHandling.EscapeHtml }; @@ -7947,17 +7492,17 @@ private static string GetGuidsExpectedString(bool prettyPrint, string keyString, json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } - private static string GetNumbersExpectedString(bool prettyPrint, int numberOfElements, T value) + private static string GetNumbersExpectedString(JsonWriterOptions options, int numberOfElements, T value) { var sb = new StringBuilder(); StringWriter stringWriter = new StringWriter(sb); var json = new JsonTextWriter(stringWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + Formatting = options.Indented ? Formatting.Indented : Formatting.None, }; json.WriteStartArray(); @@ -7969,17 +7514,17 @@ private static string GetNumbersExpectedString(bool prettyPrint, int numberOf json.Flush(); - return sb.ToString(); + return HandleIndent(sb.ToString(), options); } - private static string GetDatesExpectedString(bool prettyPrint, string keyString, DateTime[] dates, bool escape = false) + private static string GetDatesExpectedString(JsonWriterOptions options, string keyString, DateTime[] dates, bool escape = false) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + Formatting = options.Indented ? Formatting.Indented : Formatting.None, StringEscapeHandling = StringEscapeHandling.EscapeHtml, }; @@ -8001,17 +7546,17 @@ private static string GetDatesExpectedString(bool prettyPrint, string keyString, json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } - private static string GetDatesExpectedString(bool prettyPrint, string keyString, DateTimeOffset[] dates, bool escape = false) + private static string GetDatesExpectedString(JsonWriterOptions options, string keyString, DateTimeOffset[] dates, bool escape = false) { var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + Formatting = options.Indented ? Formatting.Indented : Formatting.None, StringEscapeHandling = StringEscapeHandling.EscapeHtml, }; @@ -8033,7 +7578,7 @@ private static string GetDatesExpectedString(bool prettyPrint, string keyString, json.Flush(); - return Encoding.UTF8.GetString(ms.ToArray()); + return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); } private static void CompensateWhitespaces(bool prettyPrint, JsonTextWriter json, TextWriter streamWriter, int whitespaceCount = 1) @@ -8054,6 +7599,13 @@ private static void CompensateNewLine(bool prettyPrint, JsonTextWriter json, Tex } } + private static string HandleIndent(string text, JsonWriterOptions options) + { + return text.Replace(" ", GetIndentText(options)); + } + + private static string GetIndentText(JsonWriterOptions options) => new(options.IndentCharacter, options.IndentSize); + public static IEnumerable JsonEncodedTextStrings { get @@ -8069,6 +7621,35 @@ public static IEnumerable JsonEncodedTextStrings }; } } + + private static IEnumerable JsonOptions() + { + return from indented in new[] { true, false } + from skipValidation in new[] { true, false } + from indentCharacter in indented ? new char?[] { null, ' ', '\t' } : [] + from indentSize in indented ? new int?[] { null, 0, 1, 2, 127 } : [] + select CreateOptions(indented, indentCharacter, indentSize, skipValidation); + + static JsonWriterOptions CreateOptions(bool indented, char? indentCharacter, int? indentSize, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = indented, SkipValidation = skipValidation }; + if (indentCharacter is not null) options.IndentCharacter = (char)indentCharacter; + if (indentSize is not null) options.IndentSize = (int)indentSize; + return options; + } + } + + private static IEnumerable JsonOptionsWith(IEnumerable others) => + from options in JsonOptions() + from inputValue in others + select new object[] { options, inputValue }; + + private static IEnumerable JsonOptionsWith(IEnumerable others, IEnumerable anothers) => + from options in JsonOptions() + from inputValue in others + from anotherValue in anothers + select new object[] { options, inputValue, anotherValue }; + } public static class WriterHelpers From ffdb3e4f83d2423701cf9669d4c0655f64a22dcd Mon Sep 17 00:00:00 2001 From: Zoltan Varga Date: Mon, 8 Jan 2024 12:33:54 -0500 Subject: [PATCH 07/20] [mono][jit] Optimize the experimental-gshared-mrgctx code (#96104) * [mono][jit] Avoid creating rgctx trampolines to call gshared methods which don't use their mrgctx arg. * Implement MONO_ARCH_HAVE_INIT_MRGCTX for x64. * Implement a cross-platform optimized version of OP_INIT_MRGCTX. --- src/mono/mono/metadata/jit-info.c | 2 ++ src/mono/mono/metadata/jit-info.h | 7 +++- src/mono/mono/mini/cpu-amd64.mdesc | 1 + src/mono/mono/mini/method-to-ir.c | 42 +++++++++++++++++++---- src/mono/mono/mini/mini-amd64.c | 23 +++++++++++++ src/mono/mono/mini/mini-amd64.h | 2 ++ src/mono/mono/mini/mini-arm64.c | 2 +- src/mono/mono/mini/mini-generic-sharing.c | 17 +++++++++ src/mono/mono/mini/mini-trampolines.c | 2 +- src/mono/mono/mini/mini.c | 14 ++++++++ src/mono/mono/mini/mini.h | 5 +++ 11 files changed, 108 insertions(+), 9 deletions(-) diff --git a/src/mono/mono/metadata/jit-info.c b/src/mono/mono/metadata/jit-info.c index db8a8ab6a70f64..c159291151c707 100644 --- a/src/mono/mono/metadata/jit-info.c +++ b/src/mono/mono/metadata/jit-info.c @@ -860,6 +860,8 @@ mono_jit_info_init (MonoJitInfo *ji, MonoMethod *method, guint8 *code, int code_ ji->has_thunk_info = 1; if (flags & JIT_INFO_HAS_UNWIND_INFO) ji->has_unwind_info = 1; + if (flags & JIT_INFO_NO_MRGCTX) + ji->no_mrgctx = 1; } /** diff --git a/src/mono/mono/metadata/jit-info.h b/src/mono/mono/metadata/jit-info.h index 0600b4738d3d93..a677940458c2a1 100644 --- a/src/mono/mono/metadata/jit-info.h +++ b/src/mono/mono/metadata/jit-info.h @@ -173,7 +173,11 @@ typedef enum { * If this is set, the unwind info is stored in the structure, instead of being pointed to by the * 'unwind_info' field. */ - JIT_INFO_HAS_UNWIND_INFO = (1 << 4) + JIT_INFO_HAS_UNWIND_INFO = (1 << 4), + /* + * gshared method which doesn't use its mrgctx arg. + */ + JIT_INFO_NO_MRGCTX = (1 << 5) } MonoJitInfoFlags; G_ENUM_FUNCTIONS (MonoJitInfoFlags) @@ -217,6 +221,7 @@ struct _MonoJitInfo { guint is_trampoline:1; /* Whenever this jit info refers to an interpreter method */ guint is_interp:1; + guint no_mrgctx:1; /* FIXME: Embed this after the structure later*/ gpointer gc_info; /* Currently only used by SGen */ diff --git a/src/mono/mono/mini/cpu-amd64.mdesc b/src/mono/mono/mini/cpu-amd64.mdesc index a380b4dc7b4a1d..2658508e72b0d9 100644 --- a/src/mono/mono/mini/cpu-amd64.mdesc +++ b/src/mono/mono/mini/cpu-amd64.mdesc @@ -855,6 +855,7 @@ gc_param_slot_liveness_def: len:0 generic_class_init: src1:A len:32 clob:c get_last_error: dest:i len:32 +init_mrgctx: src1:A src2:i len:64 clob:c fill_prof_call_ctx: src1:i len:128 diff --git a/src/mono/mono/mini/method-to-ir.c b/src/mono/mono/mini/method-to-ir.c index 1f359e31cc6638..eaf21803418bef 100644 --- a/src/mono/mono/mini/method-to-ir.c +++ b/src/mono/mono/mini/method-to-ir.c @@ -1364,6 +1364,10 @@ mono_create_rgctx_var (MonoCompile *cfg) /* force the var to be stack allocated */ if (!cfg->llvm_only) cfg->rgctx_var->flags |= MONO_INST_VOLATILE; + if (cfg->verbose_level > 2) { + printf ("\trgctx : "); + mono_print_ins (cfg->rgctx_var); + } } } @@ -6786,22 +6790,48 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b info->entries = (MonoRuntimeGenericContextInfoTemplate *)mono_mempool_alloc0 (cfg->mempool, sizeof (MonoRuntimeGenericContextInfoTemplate) * info->count_entries); cfg->gshared_info = info; - // FIXME: Optimize this ? args [0] = mono_get_mrgctx_var (cfg); - if (cfg->compile_aot) - args [1] = mini_emit_runtime_constant (cfg, MONO_PATCH_INFO_GSHARED_METHOD_INFO, info); - else - EMIT_NEW_PCONST (cfg, args [1], info); - cfg->init_method_rgctx_ins_arg = args [1]; if (COMPILE_LLVM (cfg) || cfg->backend->have_init_mrgctx) { + if (cfg->compile_aot) + args [1] = mini_emit_runtime_constant (cfg, MONO_PATCH_INFO_GSHARED_METHOD_INFO, info); + else + EMIT_NEW_PCONST (cfg, args [1], info); + cfg->init_method_rgctx_ins_arg = args [1]; + MONO_INST_NEW (cfg, ins, OP_INIT_MRGCTX); ins->sreg1 = args [0]->dreg; ins->sreg2 = args [1]->dreg; MONO_ADD_INS (cfg->cbb, ins); cfg->init_method_rgctx_ins = ins; + cfg->has_calls = TRUE; } else { + MonoBasicBlock *end_bb; + int mrgctx_reg, entries_reg; + + NEW_BBLOCK (cfg, end_bb); + + mrgctx_reg = mono_get_mrgctx_var (cfg)->dreg; + entries_reg = alloc_preg (cfg); + MONO_EMIT_NEW_LOAD_MEMBASE (cfg, entries_reg, mrgctx_reg, MONO_STRUCT_OFFSET (MonoMethodRuntimeGenericContext, entries)); + cfg->init_method_rgctx_ins_load = cfg->cbb->last_ins; + MONO_EMIT_NEW_BIALU_IMM (cfg, OP_COMPARE_IMM, -1, entries_reg, 0); + MONO_EMIT_NEW_BRANCH_BLOCK (cfg, OP_PBNE_UN, end_bb); + + /* Slowpath */ + cfg->cbb->out_of_line = TRUE; + if (cfg->compile_aot) + args [1] = mini_emit_runtime_constant (cfg, MONO_PATCH_INFO_GSHARED_METHOD_INFO, info); + else + EMIT_NEW_PCONST (cfg, args [1], info); + // FIXME: Eliminate the whole code not just the slowpath + cfg->init_method_rgctx_ins_arg = args [1]; cfg->init_method_rgctx_ins = mono_emit_jit_icall (cfg, mini_init_method_rgctx, args); + MONO_EMIT_NEW_BRANCH_BLOCK (cfg, OP_BR, end_bb); + + MONO_START_BB (cfg, end_bb); + init_localsbb = cfg->cbb; + init_localsbb2 = cfg->cbb; } } diff --git a/src/mono/mono/mini/mini-amd64.c b/src/mono/mono/mini/mini-amd64.c index a45878e16e7d7b..a286d33e2439bf 100644 --- a/src/mono/mono/mini/mini-amd64.c +++ b/src/mono/mono/mini/mini-amd64.c @@ -5694,6 +5694,29 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) x86_patch (jump, code); break; } + case OP_INIT_MRGCTX: { + int field_offset; + guint8 *jump; + + field_offset = MONO_STRUCT_OFFSET (MonoMethodRuntimeGenericContext, entries); + + g_assert (ins->sreg1 == MONO_AMD64_ARG_REG1); + + amd64_mov_reg_membase (code, GP_SCRATCH_REG, ins->sreg1, field_offset, sizeof (gpointer)); + amd64_test_reg_reg (code, GP_SCRATCH_REG, GP_SCRATCH_REG); + jump = code; + amd64_branch8 (code, X86_CC_NZ, -1, 1); + + /* Slowpath */ + if (ins->sreg2 != MONO_AMD64_ARG_REG2) + amd64_mov_reg_reg (code, MONO_AMD64_ARG_REG2, ins->sreg2, sizeof (target_mgreg_t)); + code = emit_call (cfg, NULL, code, MONO_JIT_ICALL_mini_init_method_rgctx); + ins->flags |= MONO_INST_GC_CALLSITE; + ins->backend.pc_offset = GPTRDIFF_TO_INT (code - cfg->native_code); + + x86_patch (jump, code); + break; + } case OP_X86_LEA: amd64_lea_memindex (code, ins->dreg, ins->sreg1, ins->inst_imm, ins->sreg2, ins->backend.shift_amount); diff --git a/src/mono/mono/mini/mini-amd64.h b/src/mono/mono/mini/mini-amd64.h index 2c775c1585545b..ce508e0a217918 100644 --- a/src/mono/mono/mini/mini-amd64.h +++ b/src/mono/mono/mini/mini-amd64.h @@ -459,6 +459,8 @@ typedef struct { #define MONO_ARCH_HAVE_INTERP_PINVOKE_TRAMP #define MONO_ARCH_HAVE_INTERP_ENTRY_TRAMPOLINE 1 #define MONO_ARCH_HAVE_INTERP_NATIVE_TO_MANAGED 1 +// FIXME: Doesn't work on windows +//#define MONO_ARCH_HAVE_INIT_MRGCTX 1 #if defined(TARGET_OSX) || defined(__linux__) #define MONO_ARCH_HAVE_UNWIND_BACKTRACE 1 diff --git a/src/mono/mono/mini/mini-arm64.c b/src/mono/mono/mini/mini-arm64.c index b924f1be71f115..dafaabaa5ba06f 100644 --- a/src/mono/mono/mini/mini-arm64.c +++ b/src/mono/mono/mini/mini-arm64.c @@ -6120,7 +6120,7 @@ mono_arch_emit_prolog (MonoCompile *cfg) } /* Save mrgctx received in MONO_ARCH_RGCTX_REG */ - if (cfg->rgctx_var) { + if (cfg->rgctx_var && !cfg->init_method_rgctx_elim) { MonoInst *ins = cfg->rgctx_var; g_assert (ins->opcode == OP_REGOFFSET); diff --git a/src/mono/mono/mini/mini-generic-sharing.c b/src/mono/mono/mini/mini-generic-sharing.c index cf0a787cf5f953..568d62cfeadec7 100644 --- a/src/mono/mono/mini/mini-generic-sharing.c +++ b/src/mono/mono/mini/mini-generic-sharing.c @@ -3775,6 +3775,23 @@ mono_method_needs_static_rgctx_invoke (MonoMethod *method, gboolean allow_type_v (mono_class_is_ginst (method->klass) || mono_class_is_gtd (method->klass)); } +/* + * mono_method_needs_mrgctx_arg_for_eh: + * + * Return TRUE if the mrgctx arg to the gshared method METHOD cannot be eliminated. + */ +gboolean +mono_method_needs_mrgctx_arg_for_eh (MonoMethod *method) +{ + if (method->is_inflated && mono_method_get_context (method)->method_inst) + return TRUE; + + return ((method->flags & METHOD_ATTRIBUTE_STATIC) || + m_class_is_valuetype (method->klass) || + mini_method_is_default_method (method)) && + (mono_class_is_ginst (method->klass) || mono_class_is_gtd (method->klass)); +} + static MonoGenericInst* get_object_generic_inst (int type_argc) { diff --git a/src/mono/mono/mini/mini-trampolines.c b/src/mono/mono/mini/mini-trampolines.c index 92802d83ae668a..15946671704bfe 100644 --- a/src/mono/mono/mini/mini-trampolines.c +++ b/src/mono/mono/mini/mini-trampolines.c @@ -419,7 +419,7 @@ mini_add_method_trampoline (MonoMethod *m, gpointer compiled_method, gboolean ad if (mono_llvm_only) add_static_rgctx_tramp = FALSE; - if (add_static_rgctx_tramp) + if (add_static_rgctx_tramp && !(ji && ji->no_mrgctx)) addr = mono_create_static_rgctx_trampoline (m, addr); return addr; diff --git a/src/mono/mono/mini/mini.c b/src/mono/mono/mini/mini.c index b0127a78b31999..b6935d312c6ee8 100644 --- a/src/mono/mono/mini/mini.c +++ b/src/mono/mono/mini/mini.c @@ -2310,6 +2310,8 @@ create_jit_info (MonoCompile *cfg, MonoMethod *method_to_compile) if (cfg->gshared) flags |= JIT_INFO_HAS_GENERIC_JIT_INFO; + if (cfg->init_method_rgctx_elim) + flags |= JIT_INFO_NO_MRGCTX; if (cfg->arch_eh_jit_info) { MonoJitArgumentInfo *arg_info; @@ -3793,6 +3795,18 @@ mini_method_compile (MonoMethod *method, guint32 opts, JitFlags flags, int parts cfg->init_method_rgctx_ins_arg->opcode = OP_PCONST; cfg->init_method_rgctx_ins_arg->inst_p0 = NULL; MONO_INST_NULLIFY_SREGS (cfg->init_method_rgctx_ins_arg); + if (cfg->init_method_rgctx_ins_load) { + cfg->init_method_rgctx_ins_load->opcode = OP_PCONST; + cfg->init_method_rgctx_ins_load->inst_p0 = GINT_TO_POINTER (0x1); + MONO_INST_NULLIFY_SREGS (cfg->init_method_rgctx_ins_load); + } + + /* + * Avoid creating rgctx trampolines when calling this method. + * Static/vtype etc. methods still need an rgctx arg for EH. + */ + if (!mono_method_needs_mrgctx_arg_for_eh (cfg->method)) + cfg->init_method_rgctx_elim = TRUE; } if (cfg->got_var) { diff --git a/src/mono/mono/mini/mini.h b/src/mono/mono/mini/mini.h index 36a883139c9c7d..cd50bb21ba96ab 100644 --- a/src/mono/mono/mini/mini.h +++ b/src/mono/mono/mini/mini.h @@ -1417,6 +1417,7 @@ typedef struct { /* Points to the call to mini_init_method_rgctx () */ MonoInst *init_method_rgctx_ins; MonoInst *init_method_rgctx_ins_arg; + MonoInst *init_method_rgctx_ins_load; MonoInst *lmf_var; MonoInst *lmf_addr_var; @@ -1514,6 +1515,7 @@ typedef struct { guint disable_inline_rgctx_fetch : 1; guint deopt : 1; guint prefer_instances : 1; + guint init_method_rgctx_elim : 1; guint8 uses_simd_intrinsics; int r4_stack_type; gpointer debug_info; @@ -2724,6 +2726,9 @@ mini_rgctx_info_type_to_patch_info_type (MonoRgctxInfoType info_type); gboolean mono_method_needs_static_rgctx_invoke (MonoMethod *method, gboolean allow_type_vars); +gboolean +mono_method_needs_mrgctx_arg_for_eh (MonoMethod *method); + int mono_class_rgctx_get_array_size (int n, gboolean mrgctx); From 96851fc7d20656abaf7fe4f05ec0eddb9c76906e Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Mon, 8 Jan 2024 09:37:47 -0800 Subject: [PATCH 08/20] Fix generic signature issue when calling non generic method of closed generic type (#96517) * Fix generic signature issue when calling non generic method of closed generic type * Apply feedbacks --- .../Reflection/Emit/ModuleBuilderImpl.cs | 23 +- .../AssemblySaveILGeneratorTests.cs | 358 ++++++++++-------- 2 files changed, 226 insertions(+), 155 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs index f3ff1297352729..f753a862e22874 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs @@ -535,8 +535,9 @@ private EntityHandle GetMemberReferenceHandle(MemberInfo member) return memberHandle; } - private EntityHandle GetMethodReference(MethodInfo method, Type[] optionalParameterTypes) + private EntityHandle GetMethodReference(MethodInfo methodInfo, Type[] optionalParameterTypes) { + MethodInfo method = (MethodInfo)GetOriginalMemberIfConstructedType(methodInfo); BlobBuilder signature = GetMethodSignature(method, optionalParameterTypes); KeyValuePair pair = new(method, signature); if (!_memberReferences.TryGetValue(pair, out var memberHandle)) @@ -549,16 +550,28 @@ private EntityHandle GetMethodReference(MethodInfo method, Type[] optionalParame return memberHandle; } - private BlobBuilder GetMethodSignature(MethodInfo method, Type[] optionalParameterTypes) => + private BlobBuilder GetMethodSignature(MethodInfo method, Type[]? optionalParameterTypes) => MetadataSignatureHelper.GetMethodSignature(this, ParameterTypes(method.GetParameters()), method.ReturnType, MethodBuilderImpl.GetSignatureConvention(method.CallingConvention), method.GetGenericArguments().Length, !method.IsStatic, optionalParameterTypes); - private BlobBuilder GetMemberSignature(MemberInfo member) + private static MemberInfo GetOriginalMemberIfConstructedType(MemberInfo memberInfo) { + Type declaringType = memberInfo.DeclaringType!; + if (declaringType.IsConstructedGenericType && declaringType.GetGenericTypeDefinition() is not TypeBuilderImpl) + { + return declaringType.GetGenericTypeDefinition().GetMemberWithSameMetadataDefinitionAs(memberInfo); + } + + return memberInfo; + } + + private BlobBuilder GetMemberSignature(MemberInfo memberInfo) + { + MemberInfo member = GetOriginalMemberIfConstructedType(memberInfo); + if (member is MethodInfo method) { - return MetadataSignatureHelper.GetMethodSignature(this, ParameterTypes(method.GetParameters()), method.ReturnType, - MethodBuilderImpl.GetSignatureConvention(method.CallingConvention), method.GetGenericArguments().Length, !method.IsStatic); + return GetMethodSignature(method, optionalParameterTypes: null); } if (member is FieldInfo field) diff --git a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs index 45113f265eaae4..afa42d5f0588b1 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using Microsoft.DotNet.RemoteExecutor; using Xunit; namespace System.Reflection.Emit.Tests @@ -1591,67 +1592,75 @@ public void DeeperNestedTryCatchFilterFinallyBlocks() private static int Int32Sum(int a, int b) => a + b; - [Fact] + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void EmitCalliBlittable() { - int a = 1, b = 1, result = 2; - using (TempFile file = TempFile.Create()) + RemoteExecutor.Invoke(() => { - AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndSaveMethod(new AssemblyName("EmitCalliBlittable"), out MethodInfo saveMethod); - TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); - Type returnType = typeof(int); - MethodBuilder methodBuilder = tb.DefineMethod("F", MethodAttributes.Public | MethodAttributes.Static, returnType, [typeof(IntPtr), typeof(int), typeof(int)]); - methodBuilder.SetImplementationFlags(MethodImplAttributes.NoInlining); - ILGenerator il = methodBuilder.GetILGenerator(); - il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldarg_2); - il.Emit(OpCodes.Ldarg_0); - il.EmitCalli(OpCodes.Calli, CallingConvention.StdCall, returnType, [typeof(int), typeof(int)]); - il.Emit(OpCodes.Ret); - tb.CreateType(); - saveMethod.Invoke(ab, [file.Path]); - - Assembly assemblyFromDisk = Assembly.LoadFrom(file.Path); - Type typeFromDisk = assemblyFromDisk.GetType("MyType"); - var del = new Int32SumStdCall(Int32Sum); - IntPtr funcPtr = Marshal.GetFunctionPointerForDelegate(del); - object resultValue = typeFromDisk.GetMethod("F", BindingFlags.Public | BindingFlags.Static).Invoke(null, [funcPtr, a, b]); - GC.KeepAlive(del); - - Assert.IsType(returnType, resultValue); - Assert.Equal(result, resultValue); - } + int a = 1, b = 1, result = 2; + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndSaveMethod(new AssemblyName("EmitCalliBlittable"), out MethodInfo saveMethod); + TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); + Type returnType = typeof(int); + MethodBuilder methodBuilder = tb.DefineMethod("F", MethodAttributes.Public | MethodAttributes.Static, returnType, [typeof(IntPtr), typeof(int), typeof(int)]); + methodBuilder.SetImplementationFlags(MethodImplAttributes.NoInlining); + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldarg_2); + il.Emit(OpCodes.Ldarg_0); + il.EmitCalli(OpCodes.Calli, CallingConvention.StdCall, returnType, [typeof(int), typeof(int)]); + il.Emit(OpCodes.Ret); + tb.CreateType(); + saveMethod.Invoke(ab, [file.Path]); + + Assembly assemblyFromDisk = Assembly.LoadFrom(file.Path); + Type typeFromDisk = assemblyFromDisk.GetType("MyType"); + var del = new Int32SumStdCall(Int32Sum); + IntPtr funcPtr = Marshal.GetFunctionPointerForDelegate(del); + object resultValue = typeFromDisk.GetMethod("F", BindingFlags.Public | BindingFlags.Static).Invoke(null, [funcPtr, a, b]); + GC.KeepAlive(del); + + Assert.IsType(returnType, resultValue); + Assert.Equal(result, resultValue); + } + return RemoteExecutor.SuccessExitCode; + }).Dispose(); } - [Fact] + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void EmitCalliManagedBlittable() { - int a = 1, b = 1, result = 2; - using (TempFile file = TempFile.Create()) + RemoteExecutor.Invoke(() => { - AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndSaveMethod(new AssemblyName("EmitCalliManagedBlittable"), out MethodInfo saveMethod); - TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); - Type returnType = typeof(int); - MethodBuilder methodBuilder = tb.DefineMethod("F", MethodAttributes.Public | MethodAttributes.Static, returnType, [typeof(IntPtr), typeof(int), typeof(int)]); - methodBuilder.SetImplementationFlags(MethodImplAttributes.NoInlining); - MethodInfo method = typeof(AssemblySaveILGeneratorTests).GetMethod(nameof(Int32Sum), BindingFlags.NonPublic | BindingFlags.Static)!; - IntPtr funcPtr = method.MethodHandle.GetFunctionPointer(); - ILGenerator il = methodBuilder.GetILGenerator(); - il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldarg_2); - il.Emit(OpCodes.Ldarg_0); - il.EmitCalli(OpCodes.Calli, CallingConventions.Standard, returnType, [typeof(int), typeof(int)], null); - il.Emit(OpCodes.Ret); - tb.CreateType(); - saveMethod.Invoke(ab, [file.Path]); - - Assembly assemblyFromDisk = Assembly.LoadFrom(file.Path); - Type typeFromDisk = assemblyFromDisk.GetType("MyType"); - object resultValue = typeFromDisk.GetMethod("F", BindingFlags.Public | BindingFlags.Static).Invoke(null, [funcPtr, a, b]); - - Assert.IsType(returnType, resultValue); - Assert.Equal(result, resultValue); - } + int a = 1, b = 1, result = 2; + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndSaveMethod(new AssemblyName("EmitCalliManagedBlittable"), out MethodInfo saveMethod); + TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); + Type returnType = typeof(int); + MethodBuilder methodBuilder = tb.DefineMethod("F", MethodAttributes.Public | MethodAttributes.Static, returnType, [typeof(IntPtr), typeof(int), typeof(int)]); + methodBuilder.SetImplementationFlags(MethodImplAttributes.NoInlining); + MethodInfo method = typeof(AssemblySaveILGeneratorTests).GetMethod(nameof(Int32Sum), BindingFlags.NonPublic | BindingFlags.Static)!; + IntPtr funcPtr = method.MethodHandle.GetFunctionPointer(); + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldarg_2); + il.Emit(OpCodes.Ldarg_0); + il.EmitCalli(OpCodes.Calli, CallingConventions.Standard, returnType, [typeof(int), typeof(int)], null); + il.Emit(OpCodes.Ret); + tb.CreateType(); + saveMethod.Invoke(ab, [file.Path]); + + Assembly assemblyFromDisk = Assembly.LoadFrom(file.Path); + Type typeFromDisk = assemblyFromDisk.GetType("MyType"); + object resultValue = typeFromDisk.GetMethod("F", BindingFlags.Public | BindingFlags.Static).Invoke(null, [funcPtr, a, b]); + + Assert.IsType(returnType, resultValue); + Assert.Equal(result, resultValue); + } + return RemoteExecutor.SuccessExitCode; + }).Dispose(); } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -1659,35 +1668,39 @@ public void EmitCalliManagedBlittable() private static string StringReverse(string a) => string.Join("", a.Reverse()); - [Fact] + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void EmitCalliNonBlittable() { - string input = "Test string!", result = "!gnirts tseT"; - using (TempFile file = TempFile.Create()) + RemoteExecutor.Invoke(() => { - AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndSaveMethod(new AssemblyName("EmitCalliNonBlittable"), out MethodInfo saveMethod); - TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); - Type returnType = typeof(string); - MethodBuilder methodBuilder = tb.DefineMethod("F", MethodAttributes.Public | MethodAttributes.Static, returnType, [typeof(IntPtr), typeof(string)]); - methodBuilder.SetImplementationFlags(MethodImplAttributes.NoInlining); - ILGenerator il = methodBuilder.GetILGenerator(); - il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldarg_0); - il.EmitCalli(OpCodes.Calli, CallingConvention.Cdecl, returnType, [typeof(string)]); - il.Emit(OpCodes.Ret); - tb.CreateType(); - saveMethod.Invoke(ab, [file.Path]); - - Assembly assemblyFromDisk = Assembly.LoadFrom(file.Path); - Type typeFromDisk = assemblyFromDisk.GetType("MyType"); - var del = new StringReverseCdecl(StringReverse); - IntPtr funcPtr = Marshal.GetFunctionPointerForDelegate(del); - object resultValue = typeFromDisk.GetMethod("F", BindingFlags.Public | BindingFlags.Static).Invoke(null, [funcPtr, input]); - GC.KeepAlive(del); - - Assert.IsType(returnType, resultValue); - Assert.Equal(result, resultValue); - } + string input = "Test string!", result = "!gnirts tseT"; + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndSaveMethod(new AssemblyName("EmitCalliNonBlittable"), out MethodInfo saveMethod); + TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); + Type returnType = typeof(string); + MethodBuilder methodBuilder = tb.DefineMethod("F", MethodAttributes.Public | MethodAttributes.Static, returnType, [typeof(IntPtr), typeof(string)]); + methodBuilder.SetImplementationFlags(MethodImplAttributes.NoInlining); + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldarg_0); + il.EmitCalli(OpCodes.Calli, CallingConvention.Cdecl, returnType, [typeof(string)]); + il.Emit(OpCodes.Ret); + tb.CreateType(); + saveMethod.Invoke(ab, [file.Path]); + + Assembly assemblyFromDisk = Assembly.LoadFrom(file.Path); + Type typeFromDisk = assemblyFromDisk.GetType("MyType"); + var del = new StringReverseCdecl(StringReverse); + IntPtr funcPtr = Marshal.GetFunctionPointerForDelegate(del); + object resultValue = typeFromDisk.GetMethod("F", BindingFlags.Public | BindingFlags.Static).Invoke(null, [funcPtr, input]); + GC.KeepAlive(del); + + Assert.IsType(returnType, resultValue); + Assert.Equal(result, resultValue); + } + return RemoteExecutor.SuccessExitCode; + }).Dispose(); } [Fact] @@ -2093,86 +2106,131 @@ static void GetCode() } } - [Fact] + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void SimpleForLoopTest() { - using (TempFile file = TempFile.Create()) + RemoteExecutor.Invoke(() => { - AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); - MethodBuilder mb2 = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int)]); - ILGenerator il = mb2.GetILGenerator(); - LocalBuilder sum = il.DeclareLocal(typeof(int)); - LocalBuilder i = il.DeclareLocal(typeof(int)); - Label loopEnd = il.DefineLabel(); - Label loopStart = il.DefineLabel(); - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Stloc_0); - il.Emit(OpCodes.Ldc_I4_1); - il.Emit(OpCodes.Stloc_1); - il.MarkLabel(loopStart); - il.Emit(OpCodes.Ldloc_1); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Bgt, loopEnd); - il.Emit(OpCodes.Ldloc_0); - il.Emit(OpCodes.Ldloc_1); - il.Emit(OpCodes.Add); - il.Emit(OpCodes.Stloc_0); - il.Emit(OpCodes.Ldloc_1); - il.Emit(OpCodes.Ldc_I4_1); - il.Emit(OpCodes.Add); - il.Emit(OpCodes.Stloc_1); - il.Emit(OpCodes.Br, loopStart); - il.MarkLabel(loopEnd); - il.Emit(OpCodes.Ldloc_0); - il.Emit(OpCodes.Ret); - tb.CreateType(); - saveMethod.Invoke(ab, [file.Path]); - - MethodInfo getMaxStackMethod = GetMaxStackMethod(); - Assert.Equal(2, getMaxStackMethod.Invoke(il, null)); - - Assembly assemblyFromDisk = Assembly.LoadFrom(file.Path); - Type typeFromDisk = assemblyFromDisk.GetType("MyType"); - MethodInfo sumMethodFromDisk = typeFromDisk.GetMethod("SumMethod"); - Assert.Equal(55, sumMethodFromDisk.Invoke(null, [10])); - } + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + MethodBuilder mb2 = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int)]); + ILGenerator il = mb2.GetILGenerator(); + LocalBuilder sum = il.DeclareLocal(typeof(int)); + LocalBuilder i = il.DeclareLocal(typeof(int)); + Label loopEnd = il.DefineLabel(); + Label loopStart = il.DefineLabel(); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Stloc_0); + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Stloc_1); + il.MarkLabel(loopStart); + il.Emit(OpCodes.Ldloc_1); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Bgt, loopEnd); + il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ldloc_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc_0); + il.Emit(OpCodes.Ldloc_1); + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc_1); + il.Emit(OpCodes.Br, loopStart); + il.MarkLabel(loopEnd); + il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ret); + tb.CreateType(); + saveMethod.Invoke(ab, [file.Path]); + + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(2, getMaxStackMethod.Invoke(il, null)); + + Assembly assemblyFromDisk = Assembly.LoadFrom(file.Path); + Type typeFromDisk = assemblyFromDisk.GetType("MyType"); + MethodInfo sumMethodFromDisk = typeFromDisk.GetMethod("SumMethod"); + Assert.Equal(55, sumMethodFromDisk.Invoke(null, [10])); + } + return RemoteExecutor.SuccessExitCode; + }).Dispose(); } - [Fact] + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void RecursiveSumTest() { - using (TempFile file = TempFile.Create()) + RemoteExecutor.Invoke(() => { - AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndSaveMethod(new AssemblyName("RecursiveSumTest"), out MethodInfo saveMethod); - TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); - MethodBuilder mb2 = tb.DefineMethod("RecursiveMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int)]); - ILGenerator il = mb2.GetILGenerator(); - Label loopEnd = il.DefineLabel(); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Ble, loopEnd); // if (value1 <= value2) - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldc_I4_1); - il.Emit(OpCodes.Sub); - il.Emit(OpCodes.Call, mb2); - il.Emit(OpCodes.Add); - il.Emit(OpCodes.Ret); - il.MarkLabel(loopEnd); - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Ret); - tb.CreateType(); - saveMethod.Invoke(ab, [file.Path]); - - MethodInfo getMaxStackMethod = GetMaxStackMethod(); - Assert.Equal(3, getMaxStackMethod.Invoke(il, null)); + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderAndSaveMethod(new AssemblyName("RecursiveSumTest"), out MethodInfo saveMethod); + TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); + MethodBuilder mb2 = tb.DefineMethod("RecursiveMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int)]); + ILGenerator il = mb2.GetILGenerator(); + Label loopEnd = il.DefineLabel(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Ble, loopEnd); // if (value1 <= value2) + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Sub); + il.Emit(OpCodes.Call, mb2); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Ret); + il.MarkLabel(loopEnd); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Ret); + tb.CreateType(); + saveMethod.Invoke(ab, [file.Path]); + + MethodInfo getMaxStackMethod = GetMaxStackMethod(); + Assert.Equal(3, getMaxStackMethod.Invoke(il, null)); + + Assembly assemblyFromDisk = Assembly.LoadFrom(file.Path); + Type typeFromDisk = assemblyFromDisk.GetType("MyType"); + MethodInfo recursiveMethodFromDisk = typeFromDisk.GetMethod("RecursiveMethod"); + Assert.NotNull(recursiveMethodFromDisk); + Assert.Equal(55, recursiveMethodFromDisk.Invoke(null, [10])); + } + return RemoteExecutor.SuccessExitCode; + }).Dispose(); + } - Assembly assemblyFromDisk = Assembly.LoadFrom(file.Path); - Type typeFromDisk = assemblyFromDisk.GetType("MyType"); - MethodInfo recursiveMethodFromDisk = typeFromDisk.GetMethod("RecursiveMethod"); - Assert.NotNull(recursiveMethodFromDisk); - Assert.Equal(55, recursiveMethodFromDisk.Invoke(null, [10])); - } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void CallOpenGenericMembersFromConstructedGenericType() + { + RemoteExecutor.Invoke(() => + { + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo saveMethod); + MethodBuilder method = type.DefineMethod("M1", MethodAttributes.Public, typeof(string), null); + + ILGenerator ilGenerator = method.GetILGenerator(); + LocalBuilder span = ilGenerator.DeclareLocal(typeof(ReadOnlySpan)); + LocalBuilder str = ilGenerator.DeclareLocal(typeof(string)); + + ilGenerator.Emit(OpCodes.Ldstr, "hello"); + ilGenerator.Emit(OpCodes.Call, typeof(MemoryExtensions).GetMethod("AsSpan", [typeof(string)])!); + ilGenerator.Emit(OpCodes.Stloc, span); + ilGenerator.Emit(OpCodes.Ldloca_S, span); + ilGenerator.Emit(OpCodes.Ldc_I4_1); + ilGenerator.Emit(OpCodes.Call, typeof(ReadOnlySpan).GetMethod("Slice", [typeof(int)])!); + ilGenerator.Emit(OpCodes.Stloc, span); + ilGenerator.Emit(OpCodes.Ldloca_S, span); + ilGenerator.Emit(OpCodes.Constrained, typeof(ReadOnlySpan)); + ilGenerator.Emit(OpCodes.Callvirt, typeof(object).GetMethod("ToString")); + ilGenerator.Emit(OpCodes.Ret); + + type.CreateType(); + saveMethod.Invoke(ab, [file.Path]); + + Type typeFromDisk = Assembly.LoadFile(file.Path).GetType("MyType"); + string result = (string)typeFromDisk.GetMethod("M1").Invoke(Activator.CreateInstance(typeFromDisk), null); + Assert.Equal("ello", result); + } + return RemoteExecutor.SuccessExitCode; + }).Dispose(); } } } From cd0e33b1e3b7403fc7c649111f1222c389e1ddc7 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 19:00:11 +0100 Subject: [PATCH 09/20] Update dependencies from https://dev.azure.com/dnceng/internal/_git/dotnet-optimization build 20240106.4 (#96619) optimization.linux-arm64.MIBC.Runtime , optimization.linux-x64.MIBC.Runtime , optimization.windows_nt-arm64.MIBC.Runtime , optimization.windows_nt-x64.MIBC.Runtime , optimization.windows_nt-x86.MIBC.Runtime , optimization.PGO.CoreCLR From Version 1.0.0-prerelease.23629.4 -> To Version 1.0.0-prerelease.24056.4 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.xml | 24 ++++++++++++------------ eng/Versions.props | 12 ++++++------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 649db28ec630d0..11e665e0298d49 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -337,21 +337,21 @@ https://github.com/dotnet/arcade 3faeb9817f465151aa4bbcdb315f0a6170206760 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - 31e28a11c828a11bd44d3dbadaf96d1da786518b + 7064208ca24e7b40f2437f61e69701fb53d53cfa - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - 31e28a11c828a11bd44d3dbadaf96d1da786518b + 7064208ca24e7b40f2437f61e69701fb53d53cfa - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - 31e28a11c828a11bd44d3dbadaf96d1da786518b + 7064208ca24e7b40f2437f61e69701fb53d53cfa - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - 31e28a11c828a11bd44d3dbadaf96d1da786518b + 7064208ca24e7b40f2437f61e69701fb53d53cfa https://github.com/dotnet/hotreload-utils @@ -387,13 +387,13 @@ c518bc5c80dbf7d9944e9489237938f57fe145fd - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - 31e28a11c828a11bd44d3dbadaf96d1da786518b + 7064208ca24e7b40f2437f61e69701fb53d53cfa - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - 31e28a11c828a11bd44d3dbadaf96d1da786518b + 7064208ca24e7b40f2437f61e69701fb53d53cfa diff --git a/eng/Versions.props b/eng/Versions.props index d425601078eb1e..ea5219971d280b 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -154,12 +154,12 @@ 9.0.0-beta.23615.1 9.0.0-beta.23615.1 - 1.0.0-prerelease.23629.4 - 1.0.0-prerelease.23629.4 - 1.0.0-prerelease.23629.4 - 1.0.0-prerelease.23629.4 - 1.0.0-prerelease.23629.4 - 1.0.0-prerelease.23629.4 + 1.0.0-prerelease.24056.4 + 1.0.0-prerelease.24056.4 + 1.0.0-prerelease.24056.4 + 1.0.0-prerelease.24056.4 + 1.0.0-prerelease.24056.4 + 1.0.0-prerelease.24056.4 16.11.29-beta1.23404.4 2.0.0-beta4.23407.1 From f0ca0fa9b1952500c6e01313a1d2b556f6b838e4 Mon Sep 17 00:00:00 2001 From: Fan Yang <52458914+fanyang-mono@users.noreply.github.com> Date: Mon, 8 Jan 2024 13:24:25 -0500 Subject: [PATCH 10/20] [mono][amd64] Fix Vector128 relational comparison API's for nuint type (#96515) * Fix uint greatetThan * Enable disabled tests * Add a new test --- .../tests/TensorPrimitivesTests.cs | 8 -------- .../tests/Vectors/Vector128Tests.cs | 7 +++++++ src/mono/mono/mini/mini-amd64.c | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs index 9083d7d8bf8962..8cc159b5b3b14c 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitivesTests.cs @@ -1411,7 +1411,6 @@ public void MaxMagnitude_Tensor_ThrowsForEmpty() Assert.Throws(() => MaxMagnitude(ReadOnlySpan.Empty)); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/96443", TestRuntimes.Mono)] [Fact] public void MaxMagnitude_Tensor() { @@ -1483,7 +1482,6 @@ public void MaxMagnitude_Tensor_Negative0LesserThanPositive0() Assert.Equal(ConvertFromSingle(1), MaxMagnitude( [ConvertFromSingle(-0f), ConvertFromSingle(-0f), ConvertFromSingle(-0f), ConvertFromSingle(-0f), ConvertFromSingle(-1), ConvertFromSingle(-0f), ConvertFromSingle(0f), ConvertFromSingle(1)])); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/96443", TestRuntimes.Mono)] [Fact] public void MaxMagnitude_TwoTensors() { @@ -1502,7 +1500,6 @@ public void MaxMagnitude_TwoTensors() }); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/96443", TestRuntimes.Mono)] [Fact] public void MaxMagnitude_TwoTensors_InPlace() { @@ -1531,7 +1528,6 @@ public void MaxMagnitude_TwoTensors_InPlace() }); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/96443", TestRuntimes.Mono)] [Fact] public void MaxMagnitude_TwoTensors_SpecialValues() { @@ -1789,7 +1785,6 @@ public void MinMagnitude_Tensor_ThrowsForEmpty() Assert.Throws(() => MinMagnitude(ReadOnlySpan.Empty)); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/96443", TestRuntimes.Mono)] [Fact] public void MinMagnitude_Tensor() { @@ -1859,7 +1854,6 @@ public void MinMagnitude_Tensor_Negative0LesserThanPositive0() Assert.Equal(ConvertFromSingle(0), MinMagnitude([ConvertFromSingle(-1), ConvertFromSingle(-0f), ConvertFromSingle(1)])); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/96443", TestRuntimes.Mono)] [Fact] public void MinMagnitude_TwoTensors() { @@ -1878,7 +1872,6 @@ public void MinMagnitude_TwoTensors() }); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/96443", TestRuntimes.Mono)] [Fact] public void MinMagnitude_TwoTensors_InPlace() { @@ -1907,7 +1900,6 @@ public void MinMagnitude_TwoTensors_InPlace() }); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/96443", TestRuntimes.Mono)] [Fact] public void MinMagnitude_TwoTensors_SpecialValues() { diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector128Tests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector128Tests.cs index e60093a826f704..3e8fc990ee510b 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector128Tests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector128Tests.cs @@ -4544,6 +4544,13 @@ public void Vector128SByteAbs_MinValue() } } + [Fact] + public void Vector128NuintGreaterThan_MaxValue() + { + Vector128 vector = Vector128.Create(nuint.MaxValue); + Assert.True(Vector128.EqualsAll(Vector128.GreaterThan(vector, Vector128.Zero), vector)); + } + [Fact] public void IsSupportedByte() => TestIsSupported(); diff --git a/src/mono/mono/mini/mini-amd64.c b/src/mono/mono/mini/mini-amd64.c index a286d33e2439bf..e58703d3e2b649 100644 --- a/src/mono/mono/mini/mini-amd64.c +++ b/src/mono/mono/mini/mini-amd64.c @@ -3415,9 +3415,9 @@ simd_type_to_shl_op (int t) case MONO_TYPE_I: case MONO_TYPE_U: #if TARGET_SIZEOF_VOID_P == 8 - return OP_PSHLD; -#else return OP_PSHLQ; +#else + return OP_PSHLD; #endif default: g_assert_not_reached (); From 425e74c6a0d539fbf0612b2e2d72dcf3fe520bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20K=C3=B6plinger?= Date: Mon, 8 Jan 2024 19:33:24 +0100 Subject: [PATCH 11/20] [wasm] Fix a few more paths to in-tree emsdk (#96625) It was moved from src/mono/wasm to src/mono/browser in https://github.com/dotnet/runtime/pull/95940 --- .devcontainer/wasm/devcontainer.json | 2 +- docs/workflow/debugging/mono/wasm-debugging.md | 2 +- src/mono/browser/README.md | 4 ++-- src/native/libs/build-native.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.devcontainer/wasm/devcontainer.json b/.devcontainer/wasm/devcontainer.json index ff3a23960d7d4f..ab598dcb9a32d2 100644 --- a/.devcontainer/wasm/devcontainer.json +++ b/.devcontainer/wasm/devcontainer.json @@ -49,7 +49,7 @@ "PATH": "${containerWorkspaceFolder}/.dotnet:${containerWorkspaceFolder}/.dotnet-tools-global:${containerEnv:PATH}", "DOTNET_MULTILEVEL_LOOKUP": "0", // Path to provisioned Emscripten SDK, for rebuilding the wasm runtime - "EMSDK_PATH": "${containerWorkspaceFolder}/src/mono/wasm/emsdk", + "EMSDK_PATH": "${containerWorkspaceFolder}/src/mono/browser/emsdk", }, // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. diff --git a/docs/workflow/debugging/mono/wasm-debugging.md b/docs/workflow/debugging/mono/wasm-debugging.md index 832020229b2ab8..59014ef147e28d 100644 --- a/docs/workflow/debugging/mono/wasm-debugging.md +++ b/docs/workflow/debugging/mono/wasm-debugging.md @@ -86,7 +86,7 @@ There is another random number generator in `upstream/emscripten/src/determinist which needs the same treatment. Running `make patch-deterministic` in `src/mono/wasm` will patch the -emscripten installation in `src/mono/wasm/emsdk` with these changes. +emscripten installation in `src/mono/browser/emsdk` with these changes. # Debugging signature mismatch errors diff --git a/src/mono/browser/README.md b/src/mono/browser/README.md index 53eae5109d1af5..a603ddc7be19d7 100644 --- a/src/mono/browser/README.md +++ b/src/mono/browser/README.md @@ -1,12 +1,12 @@ # Build WebAssembly -If you haven't already done so, please read [this document](../../../docs/workflow/README.md#Build_Requirements) to understand the build requirements for your operating system. If you are specifically interested in building libraries for WebAssembly, read [Libraries WebAssembly](../../../docs/workflow/building/libraries/webassembly-instructions.md). Emscripten that is needed to build the project will be provisioned automatically, unless `EMSDK_PATH` variable is set or emscripten is already present in `src\mono\wasm\emsdk` directory. +If you haven't already done so, please read [this document](../../../docs/workflow/README.md#Build_Requirements) to understand the build requirements for your operating system. If you are specifically interested in building libraries for WebAssembly, read [Libraries WebAssembly](../../../docs/workflow/building/libraries/webassembly-instructions.md). Emscripten that is needed to build the project will be provisioned automatically, unless `EMSDK_PATH` variable is set or emscripten is already present in `src\mono\browser\emsdk` directory. ### Windows Windows build [requirements](../../../docs/workflow/requirements/windows-requirements.md) -**Note:** The EMSDK has an implicit dependency on Python for it to be initialized. A consequence of this is that if the system doesn't have Python installed prior to attempting a build, the automatic provisioning will fail and be in an invalid state. Therefore, if Python needs to be installed after a build attempt the `$reporoot/src/mono/wasm/emsdk` directory should be manually deleted and then a rebuild attempted. +**Note:** The EMSDK has an implicit dependency on Python for it to be initialized. A consequence of this is that if the system doesn't have Python installed prior to attempting a build, the automatic provisioning will fail and be in an invalid state. Therefore, if Python needs to be installed after a build attempt the `$reporoot/src/mono/browser/emsdk` directory should be manually deleted and then a rebuild attempted. ## Building diff --git a/src/native/libs/build-native.sh b/src/native/libs/build-native.sh index 430174429889cc..3e90b3805aaec9 100755 --- a/src/native/libs/build-native.sh +++ b/src/native/libs/build-native.sh @@ -55,7 +55,7 @@ source "$__RepoRootDir"/eng/native/build-commons.sh # Set cross build if [[ "$__TargetOS" == browser ]]; then if [[ -z "$EMSDK_PATH" ]]; then - if [[ -d "$__RepoRootDir"/src/mono/wasm/emsdk/ ]]; then + if [[ -d "$__RepoRootDir"/src/mono/browser/emsdk/ ]]; then export EMSDK_PATH="$__RepoRootDir"/src/mono/browser/emsdk/ else echo "Error: You need to set the EMSDK_PATH environment variable pointing to the emscripten SDK root." From e2f58fe28fd3ad99c7695ac2c5c6fe5871374b27 Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Mon, 8 Jan 2024 13:39:15 -0500 Subject: [PATCH 12/20] [android] Bump to win 11 helix queue (#96139) --- eng/pipelines/coreclr/templates/helix-queues-setup.yml | 2 +- eng/pipelines/libraries/helix-queues-setup.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/pipelines/coreclr/templates/helix-queues-setup.yml b/eng/pipelines/coreclr/templates/helix-queues-setup.yml index e2e0b20ca6d0c4..7b4ce6c6c7f431 100644 --- a/eng/pipelines/coreclr/templates/helix-queues-setup.yml +++ b/eng/pipelines/coreclr/templates/helix-queues-setup.yml @@ -44,7 +44,7 @@ jobs: # Android arm64 - ${{ if in(parameters.platform, 'android_arm64') }}: - - Windows.10.Amd64.Android.Open + - Windows.11.Amd64.Android.Open # Android x64 - ${{ if in(parameters.platform, 'android_x64') }}: diff --git a/eng/pipelines/libraries/helix-queues-setup.yml b/eng/pipelines/libraries/helix-queues-setup.yml index 7b9ef6882f630b..4b83b48c65dcac 100644 --- a/eng/pipelines/libraries/helix-queues-setup.yml +++ b/eng/pipelines/libraries/helix-queues-setup.yml @@ -97,7 +97,7 @@ jobs: - ${{ if in(parameters.platform, 'android_x86', 'android_x64', 'linux_bionic_x64') }}: - Ubuntu.2204.Amd64.Android.29.Open - ${{ if in(parameters.platform, 'android_arm', 'android_arm64', 'linux_bionic_arm64') }}: - - Windows.10.Amd64.Android.Open + - Windows.11.Amd64.Android.Open # iOS Simulator/Mac Catalyst arm64 - ${{ if in(parameters.platform, 'maccatalyst_arm64', 'iossimulator_arm64') }}: From f44d62ca7d35431633505fc2a59297cc78425c59 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 8 Jan 2024 14:15:06 -0500 Subject: [PATCH 13/20] Replace LINQ's ArrayBuilder, LargeArrayBuilder, and SparseArrayBuilder (#96570) There's a lot of code involved in these, some of which we special-case to only build into some of the targets, and it's unnecessarily complicated. We can get most of the benefit with a simpler solution, which this attempts to provide. This commit removes those three types and replaces them with a SegmentedArrayBuilder. The changes to ToArray also obviate the need for the old Buffer type. It existed to avoid one array allocation at the end of loading an enumerable, where we'd doubled and doubled and doubled in size, and then eventually have an array with all the data but some extra space. Now that we don't have such an array, we don't need Buffer, and can just the normal Enumerable.ToArray. The one thing the new scheme doesn't handle as well is when there are multiple sources being added (specifically in Concat / SelectMany). Previously, the code used a complicated scheme to reserve space in the output for partial sources known to be ICollections, so it could use ICollection.CopyTo for each consistuent source. But CopyTo doesn't support partial copies, which means we can only use it if there's enough room available in an individual segment. The implementation thus tries to use it, and falls back to normal enumeration if it can't. For larger enumerations where the cost would end up being more apparent, the expectation is we'd quickly grow to a segment size where the subsequent appends are able to fit into the current segment. Hopefully. --- .../Generic/EnumerableHelpers.Linq.cs | 88 ----- .../Generic/LargeArrayBuilder.SizeOpt.cs | 44 --- .../Generic/LargeArrayBuilder.SpeedOpt.cs | 340 ------------------ .../Collections/Generic/LargeArrayBuilder.cs | 62 ---- .../Collections/Generic/SparseArrayBuilder.cs | 224 ------------ .../Common/tests/Common.Tests.csproj | 5 - .../Generic/LargeArrayBuilderTests.cs | 182 ---------- src/libraries/System.Linq/System.Linq.sln | 24 +- .../System.Linq/src/System.Linq.csproj | 16 +- .../src/System/Linq/AppendPrepend.SpeedOpt.cs | 94 +++-- .../System.Linq/src/System/Linq/Average.cs | 10 + .../System.Linq/src/System/Linq/Buffer.cs | 42 --- .../src/System/Linq/Concat.SpeedOpt.cs | 84 +++-- .../System.Linq/src/System/Linq/Enumerable.cs | 12 +- .../System.Linq/src/System/Linq/Max.cs | 10 + .../System.Linq/src/System/Linq/MaxMin.cs | 5 + .../System.Linq/src/System/Linq/Min.cs | 10 + .../System/Linq/OrderedEnumerable.SpeedOpt.cs | 84 +++-- .../src/System/Linq/OrderedEnumerable.cs | 32 +- .../src/System/Linq/Partition.SpeedOpt.cs | 10 +- .../System.Linq/src/System/Linq/Reverse.cs | 6 +- .../src/System/Linq/SegmentedArrayBuilder.cs | 313 ++++++++++++++++ .../src/System/Linq/Select.SpeedOpt.cs | 26 +- .../src/System/Linq/SelectMany.SpeedOpt.cs | 30 +- .../src/System/Linq/SingleLinkedNode.cs | 2 +- .../System.Linq/src/System/Linq/Sum.cs | 5 + .../src/System/Linq/ToCollection.cs | 54 ++- .../System.Linq/src/System/Linq/Utilities.cs | 2 +- .../src/System/Linq/Where.SpeedOpt.cs | 197 ++++------ .../System.Linq/tests/LifecycleTests.cs | 8 +- .../System.Linq/tests/ToArrayTests.cs | 5 +- 31 files changed, 714 insertions(+), 1312 deletions(-) delete mode 100644 src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.cs delete mode 100644 src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.SizeOpt.cs delete mode 100644 src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.SpeedOpt.cs delete mode 100644 src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.cs delete mode 100644 src/libraries/Common/src/System/Collections/Generic/SparseArrayBuilder.cs delete mode 100644 src/libraries/Common/tests/Tests/System/Collections/Generic/LargeArrayBuilderTests.cs delete mode 100644 src/libraries/System.Linq/src/System/Linq/Buffer.cs create mode 100644 src/libraries/System.Linq/src/System/Linq/SegmentedArrayBuilder.cs diff --git a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.cs b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.cs deleted file mode 100644 index 13970c6a0fc22b..00000000000000 --- a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Linq; - -namespace System.Collections.Generic -{ - /// - /// Internal helper functions for working with enumerables. - /// - internal static partial class EnumerableHelpers - { - /// - /// Copies items from an enumerable to an array. - /// - /// The element type of the enumerable. - /// The source enumerable. - /// The destination array. - /// The index in the array to start copying to. - /// The number of items in the enumerable. - internal static void Copy(IEnumerable source, T[] array, int arrayIndex, int count) - { - Debug.Assert(source != null); - Debug.Assert(arrayIndex >= 0); - Debug.Assert(count >= 0); - Debug.Assert(array.Length - arrayIndex >= count); - - if (source is ICollection collection) - { - Debug.Assert(collection.Count == count); - collection.CopyTo(array, arrayIndex); - return; - } - - IterativeCopy(source, array, arrayIndex, count); - } - - /// - /// Copies items from a non-collection enumerable to an array. - /// - /// The element type of the enumerable. - /// The source enumerable. - /// The destination array. - /// The index in the array to start copying to. - /// The number of items in the enumerable. - internal static void IterativeCopy(IEnumerable source, T[] array, int arrayIndex, int count) - { - Debug.Assert(source != null && !(source is ICollection)); - Debug.Assert(arrayIndex >= 0); - Debug.Assert(count >= 0); - Debug.Assert(array.Length - arrayIndex >= count); - - int endIndex = arrayIndex + count; - foreach (T item in source) - { - array[arrayIndex++] = item; - } - - Debug.Assert(arrayIndex == endIndex); - } - - /// Converts an enumerable to an array. - /// The enumerable to convert. - /// The resulting array. - internal static T[] ToArray(IEnumerable source) - { - Debug.Assert(source != null); - - if (source is ICollection collection) - { - int count = collection.Count; - if (count == 0) - { - return Array.Empty(); - } - - var result = new T[count]; - collection.CopyTo(result, arrayIndex: 0); - return result; - } - - LargeArrayBuilder builder = new(); - builder.AddRange(source); - return builder.ToArray(); - } - } -} diff --git a/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.SizeOpt.cs b/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.SizeOpt.cs deleted file mode 100644 index 50ac7317090160..00000000000000 --- a/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.SizeOpt.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; - -namespace System.Collections.Generic -{ - // LargeArrayBuilder.netcoreapp.cs provides a "LargeArrayBuilder" that's meant to help - // avoid allocations and copying while building up an array. But in doing so, it utilizes - // T[][] to store T[]s, which results in significant size increases for AOT builds. To - // address that, this minimal wrapper for ArrayBuilder may be used instead; it's a simple - // passthrough to ArrayBuilder, and thus doesn't incur the size increase due to the T[][]s. - - internal struct LargeArrayBuilder - { - private ArrayBuilder _builder; // mutable struct; do not make this readonly - - /// - /// Constructs a new builder. - /// - public LargeArrayBuilder() => this = default; - - public int Count => _builder.Count; - - public void Add(T item) => _builder.Add(item); - - public void AddRange(IEnumerable items) - { - Debug.Assert(items != null); - foreach (T item in items) - { - _builder.Add(item); - } - } - - public T[] ToArray() => _builder.ToArray(); - - public CopyPosition CopyTo(CopyPosition position, T[] array, int arrayIndex, int count) - { - Array.Copy(_builder.Buffer!, position.Column, array, arrayIndex, count); - return new CopyPosition(0, position.Column + count); - } - } -} diff --git a/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.SpeedOpt.cs b/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.SpeedOpt.cs deleted file mode 100644 index 99458c41dfe9aa..00000000000000 --- a/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.SpeedOpt.cs +++ /dev/null @@ -1,340 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Runtime.CompilerServices; - -namespace System.Collections.Generic -{ - /// - /// Helper type for building dynamically-sized arrays while minimizing allocations and copying. - /// - /// The element type. - internal struct LargeArrayBuilder - { - private const int StartingCapacity = 4; - private const int ResizeLimit = 8; - - private readonly int _maxCapacity; // The maximum capacity this builder can have. - private T[] _first; // The first buffer we store items in. Resized until ResizeLimit. - private ArrayBuilder _buffers; // After ResizeLimit * 2, we store previous buffers we've filled out here. - private T[] _current; // Current buffer we're reading into. If _count <= ResizeLimit, this is _first. - private int _index; // Index into the current buffer. - private int _count; // Count of all of the items in this builder. - - /// - /// Constructs a new builder. - /// - public LargeArrayBuilder() - : this(maxCapacity: int.MaxValue) - { - } - - /// - /// Constructs a new builder with the specified maximum capacity. - /// - /// The maximum capacity this builder can have. - /// - /// Do not add more than items to this builder. - /// - public LargeArrayBuilder(int maxCapacity) - { - Debug.Assert(maxCapacity >= 0); - - this = default; - _first = _current = Array.Empty(); - _maxCapacity = maxCapacity; - } - - /// - /// Gets the number of items added to the builder. - /// - public int Count => _count; - - /// - /// Adds an item to this builder. - /// - /// The item to add. - /// - /// Use if adding to the builder is a bottleneck for your use case. - /// Otherwise, use . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(T item) - { - Debug.Assert(_maxCapacity > _count); - - int index = _index; - T[] current = _current; - - // Must be >= and not == to enable range check elimination - if ((uint)index >= (uint)current.Length) - { - AddWithBufferAllocation(item); - } - else - { - current[index] = item; - _index = index + 1; - } - - _count++; - } - - // Non-inline to improve code quality as uncommon path - [MethodImpl(MethodImplOptions.NoInlining)] - private void AddWithBufferAllocation(T item) - { - AllocateBuffer(); - _current[_index++] = item; - } - - /// - /// Adds a range of items to this builder. - /// - /// The sequence to add. - /// - /// It is the caller's responsibility to ensure that adding - /// does not cause the builder to exceed its maximum capacity. - /// - public void AddRange(IEnumerable items) - { - Debug.Assert(items != null); - - using (IEnumerator enumerator = items.GetEnumerator()) - { - T[] destination = _current; - int index = _index; - - // Continuously read in items from the enumerator, updating _count - // and _index when we run out of space. - - while (enumerator.MoveNext()) - { - T item = enumerator.Current; - - if ((uint)index >= (uint)destination.Length) - { - AddWithBufferAllocation(item, ref destination, ref index); - } - else - { - destination[index] = item; - } - - index++; - } - - // Final update to _count and _index. - _count += index - _index; - _index = index; - } - } - - // Non-inline to improve code quality as uncommon path - [MethodImpl(MethodImplOptions.NoInlining)] - private void AddWithBufferAllocation(T item, ref T[] destination, ref int index) - { - _count += index - _index; - _index = index; - AllocateBuffer(); - destination = _current; - index = _index; - _current[index] = item; - } - - /// - /// Copies the contents of this builder to the specified array. - /// - /// The destination array. - /// The index in to start copying to. - /// The number of items to copy. - public void CopyTo(T[] array, int arrayIndex, int count) - { - Debug.Assert(arrayIndex >= 0); - Debug.Assert(count >= 0 && count <= Count); - Debug.Assert(array.Length - arrayIndex >= count); - - for (int i = 0; count > 0; i++) - { - // Find the buffer we're copying from. - T[] buffer = GetBuffer(index: i); - - // Copy until we satisfy count, or we reach the end of the buffer. - int toCopy = Math.Min(count, buffer.Length); - Array.Copy(buffer, 0, array, arrayIndex, toCopy); - - // Increment variables to that position. - count -= toCopy; - arrayIndex += toCopy; - } - } - - /// - /// Copies the contents of this builder to the specified array. - /// - /// The position in this builder to start copying from. - /// The destination array. - /// The index in to start copying to. - /// The number of items to copy. - /// The position in this builder that was copied up to. - public CopyPosition CopyTo(CopyPosition position, T[] array, int arrayIndex, int count) - { - Debug.Assert(array != null); - Debug.Assert(arrayIndex >= 0); - Debug.Assert(count > 0 && count <= Count); - Debug.Assert(array.Length - arrayIndex >= count); - - // Go through each buffer, which contains one 'row' of items. - // The index in each buffer is referred to as the 'column'. - - /* - * Visual representation: - * - * C0 C1 C2 .. C31 .. C63 - * R0: [0] [1] [2] .. [31] - * R1: [32] [33] [34] .. [63] - * R2: [64] [65] [66] .. [95] .. [127] - */ - - int row = position.Row; - int column = position.Column; - - T[] buffer = GetBuffer(row); - int copied = CopyToCore(buffer, column); - - if (count == 0) - { - return new CopyPosition(row, column + copied).Normalize(buffer.Length); - } - - do - { - buffer = GetBuffer(++row); - copied = CopyToCore(buffer, 0); - } while (count > 0); - - return new CopyPosition(row, copied).Normalize(buffer.Length); - - int CopyToCore(T[] sourceBuffer, int sourceIndex) - { - Debug.Assert(sourceBuffer.Length > sourceIndex); - - // Copy until we satisfy `count` or reach the end of the current buffer. - int copyCount = Math.Min(sourceBuffer.Length - sourceIndex, count); - Array.Copy(sourceBuffer, sourceIndex, array, arrayIndex, copyCount); - - arrayIndex += copyCount; - count -= copyCount; - - return copyCount; - } - } - - /// - /// Retrieves the buffer at the specified index. - /// - /// The index of the buffer. - public T[] GetBuffer(int index) - { - Debug.Assert(index >= 0 && index < _buffers.Count + 2); - - return index == 0 ? _first : - index <= _buffers.Count ? _buffers[index - 1] : - _current; - } - - /// - /// Adds an item to this builder. - /// - /// The item to add. - /// - /// Use if adding to the builder is a bottleneck for your use case. - /// Otherwise, use . - /// - [MethodImpl(MethodImplOptions.NoInlining)] - public void SlowAdd(T item) => Add(item); - - /// - /// Creates an array from the contents of this builder. - /// - public T[] ToArray() - { - if (TryMove(out T[] array)) - { - // No resizing to do. - return array; - } - - array = new T[_count]; - CopyTo(array, 0, _count); - return array; - } - - /// - /// Attempts to transfer this builder into an array without copying. - /// - /// The transferred array, if the operation succeeded. - /// true if the operation succeeded; otherwise, false. - public bool TryMove(out T[] array) - { - array = _first; - return _count == _first.Length; - } - - private void AllocateBuffer() - { - // - On the first few adds, simply resize _first. - // - When we pass ResizeLimit, allocate ResizeLimit elements for _current - // and start reading into _current. Set _index to 0. - // - When _current runs out of space, add it to _buffers and repeat the - // above step, except with _current.Length * 2. - // - Make sure we never pass _maxCapacity in all of the above steps. - - Debug.Assert((uint)_maxCapacity > (uint)_count); - Debug.Assert(_index == _current.Length, $"{nameof(AllocateBuffer)} was called, but there's more space."); - - // If _count is int.MinValue, we want to go down the other path which will raise an exception. - if ((uint)_count < (uint)ResizeLimit) - { - // We haven't passed ResizeLimit. Resize _first, copying over the previous items. - Debug.Assert(_current == _first && _count == _first.Length); - - int nextCapacity = Math.Min(_count == 0 ? StartingCapacity : _count * 2, _maxCapacity); - - _current = new T[nextCapacity]; - Array.Copy(_first, _current, _count); - _first = _current; - } - else - { - Debug.Assert(_maxCapacity > ResizeLimit); - Debug.Assert(_count == ResizeLimit ^ _current != _first); - - int nextCapacity; - if (_count == ResizeLimit) - { - nextCapacity = ResizeLimit; - } - else - { - // Example scenario: Let's say _count == 64. - // Then our buffers look like this: | 8 | 8 | 16 | 32 | - // As you can see, our count will be just double the last buffer. - // Now, say _maxCapacity is 100. We will find the right amount to allocate by - // doing min(64, 100 - 64). The lhs represents double the last buffer, - // the rhs the limit minus the amount we've already allocated. - - Debug.Assert(_count >= ResizeLimit * 2); - Debug.Assert(_count == _current.Length * 2); - - _buffers.Add(_current); - nextCapacity = Math.Min(_count, _maxCapacity - _count); - } - - _current = new T[nextCapacity]; - _index = 0; - } - } - } -} diff --git a/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.cs b/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.cs deleted file mode 100644 index bf28fb607b239b..00000000000000 --- a/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; - -namespace System.Collections.Generic -{ - /// - /// Represents a position within a . - /// - [DebuggerDisplay("{DebuggerDisplay,nq}")] - internal readonly struct CopyPosition - { - /// - /// Constructs a new . - /// - /// The index of the buffer to select. - /// The index within the buffer to select. - internal CopyPosition(int row, int column) - { - Debug.Assert(row >= 0); - Debug.Assert(column >= 0); - - Row = row; - Column = column; - } - - /// - /// Represents a position at the start of a . - /// - public static CopyPosition Start => default(CopyPosition); - - /// - /// The index of the buffer to select. - /// - internal int Row { get; } - - /// - /// The index within the buffer to select. - /// - internal int Column { get; } - - /// - /// If this position is at the end of the current buffer, returns the position - /// at the start of the next buffer. Otherwise, returns this position. - /// - /// The length of the current buffer. - public CopyPosition Normalize(int endColumn) - { - Debug.Assert(Column <= endColumn); - - return Column == endColumn ? - new CopyPosition(Row + 1, 0) : - this; - } - - /// - /// Gets a string suitable for display in the debugger. - /// - private string DebuggerDisplay => $"[{Row}, {Column}]"; - } -} diff --git a/src/libraries/Common/src/System/Collections/Generic/SparseArrayBuilder.cs b/src/libraries/Common/src/System/Collections/Generic/SparseArrayBuilder.cs deleted file mode 100644 index b027b21cdaccf8..00000000000000 --- a/src/libraries/Common/src/System/Collections/Generic/SparseArrayBuilder.cs +++ /dev/null @@ -1,224 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; - -namespace System.Collections.Generic -{ - /// - /// Represents a reserved region within a . - /// - [DebuggerDisplay("{DebuggerDisplay,nq}")] - internal readonly struct Marker - { - /// - /// Constructs a new marker. - /// - /// The number of items to reserve. - /// The index in the builder where this marker starts. - public Marker(int count, int index) - { - Debug.Assert(count >= 0); - Debug.Assert(index >= 0); - - Count = count; - Index = index; - } - - /// - /// The number of items to reserve. - /// - public int Count { get; } - - /// - /// The index in the builder where this marker starts. - /// - public int Index { get; } - - /// - /// Gets a string suitable for display in the debugger. - /// - private string DebuggerDisplay => $"{nameof(Index)} = {Index}, {nameof(Count)} = {Count}"; - } - - /// - /// Helper type for building arrays where sizes of certain segments are known in advance. - /// - /// The element type. - internal struct SparseArrayBuilder - { - /// - /// The underlying builder that stores items from non-reserved regions. - /// - /// - /// This field is a mutable struct; do not mark it readonly. - /// - private LargeArrayBuilder _builder; - - /// - /// The list of reserved regions within this builder. - /// - /// - /// This field is a mutable struct; do not mark it readonly. - /// - private ArrayBuilder _markers; - - /// - /// The total number of reserved slots within this builder. - /// - private int _reservedCount; - - /// - /// Constructs a new builder. - /// - public SparseArrayBuilder() - { - this = default; - _builder = new LargeArrayBuilder(); - } - - /// - /// The total number of items in this builder, including reserved regions. - /// - public int Count => checked(_builder.Count + _reservedCount); - - /// - /// The list of reserved regions in this builder. - /// - public ArrayBuilder Markers => _markers; - - /// - /// Adds an item to this builder. - /// - /// The item to add. - public void Add(T item) => _builder.Add(item); - - /// - /// Adds a range of items to this builder. - /// - /// The sequence to add. - public void AddRange(IEnumerable items) => _builder.AddRange(items); - - /// - /// Copies the contents of this builder to the specified array. - /// - /// The destination array. - /// The index in to start copying to. - /// The number of items to copy. - public void CopyTo(T[] array, int arrayIndex, int count) - { - Debug.Assert(array != null); - Debug.Assert(arrayIndex >= 0); - Debug.Assert(count >= 0 && count <= Count); - Debug.Assert(array.Length - arrayIndex >= count); - - int copied = 0; - var position = CopyPosition.Start; - - for (int i = 0; i < _markers.Count; i++) - { - Marker marker = _markers[i]; - - // During this iteration, copy until we satisfy `count` or reach the marker. - int toCopy = Math.Min(marker.Index - copied, count); - - if (toCopy > 0) - { - position = _builder.CopyTo(position, array, arrayIndex, toCopy); - - arrayIndex += toCopy; - copied += toCopy; - count -= toCopy; - } - - if (count == 0) - { - return; - } - - // We hit our marker. Advance until we satisfy `count` or fulfill `marker.Count`. - int reservedCount = Math.Min(marker.Count, count); - - arrayIndex += reservedCount; - copied += reservedCount; - count -= reservedCount; - } - - if (count > 0) - { - // Finish copying after the final marker. - _builder.CopyTo(position, array, arrayIndex, count); - } - } - - /// - /// Reserves a region starting from the current index. - /// - /// The number of items to reserve. - /// - /// This method will not make optimizations if - /// is zero; the caller is responsible for doing so. The reason for this - /// is that the number of markers needs to match up exactly with the number - /// of times was called. - /// - public void Reserve(int count) - { - Debug.Assert(count >= 0); - - _markers.Add(new Marker(count: count, index: Count)); - - checked - { - _reservedCount += count; - } - } - - /// - /// Reserves a region if the items' count can be predetermined; otherwise, adds the items to this builder. - /// - /// The items to reserve or add. - /// true if the items were reserved; otherwise, false. - /// - /// If the items' count is predetermined to be 0, no reservation is made and the return value is false. - /// The effect is the same as if the items were added, since adding an empty collection does nothing. - /// - public bool ReserveOrAdd(IEnumerable items) - { - int itemCount; - if (System.Linq.Enumerable.TryGetNonEnumeratedCount(items, out itemCount)) - { - if (itemCount > 0) - { - Reserve(itemCount); - return true; - } - } - else - { - AddRange(items); - } - return false; - } - - /// - /// Creates an array from the contents of this builder. - /// - /// - /// Regions created with will be default-initialized. - /// - public T[] ToArray() - { - // If no regions were reserved, there are no 'gaps' we need to add to the array. - // In that case, we can just call ToArray on the underlying builder. - if (_markers.Count == 0) - { - Debug.Assert(_reservedCount == 0); - return _builder.ToArray(); - } - - var array = new T[Count]; - CopyTo(array, 0, array.Length); - return array; - } - } -} diff --git a/src/libraries/Common/tests/Common.Tests.csproj b/src/libraries/Common/tests/Common.Tests.csproj index eae8a369ceff58..cded1098de094c 100644 --- a/src/libraries/Common/tests/Common.Tests.csproj +++ b/src/libraries/Common/tests/Common.Tests.csproj @@ -23,10 +23,6 @@ Link="Common\System\StringExtensions.cs" /> - - - diff --git a/src/libraries/Common/tests/Tests/System/Collections/Generic/LargeArrayBuilderTests.cs b/src/libraries/Common/tests/Tests/System/Collections/Generic/LargeArrayBuilderTests.cs deleted file mode 100644 index e3008478057cc3..00000000000000 --- a/src/libraries/Common/tests/Tests/System/Collections/Generic/LargeArrayBuilderTests.cs +++ /dev/null @@ -1,182 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Linq; -using Xunit; - -namespace System.Collections.Generic.Tests -{ - public abstract class LargeArrayBuilderTests where TGenerator : IGenerator, new() - { - private static readonly TGenerator s_generator = new TGenerator(); - - [Fact] - public void Constructor() - { - var builder = new LargeArrayBuilder(); - - Assert.Equal(0, builder.Count); - Assert.Same(Array.Empty(), builder.ToArray()); - } - - [Theory] - [MemberData(nameof(EnumerableData))] - public void AddCountAndToArray(IEnumerable seed) - { - var builder1 = new LargeArrayBuilder(); - var builder2 = new LargeArrayBuilder(); - - int count = 0; - foreach (T item in seed) - { - count++; - - builder1.Add(item); - builder2.SlowAdd(item); // Verify SlowAdd has exactly the same effect as Add. - - Assert.Equal(count, builder1.Count); - Assert.Equal(count, builder2.Count); - - Assert.Equal(seed.Take(count), builder1.ToArray()); - Assert.Equal(seed.Take(count), builder2.ToArray()); - } - } - - [Theory] - [MemberData(nameof(MaxCapacityData))] - public void MaxCapacity(IEnumerable seed, int maxCapacity) - { - var builder = new LargeArrayBuilder(maxCapacity); - - for (int i = 0; i < maxCapacity; i++) - { - builder.Add(seed.ElementAt(i)); - - int count = i + 1; - Assert.Equal(count, builder.Count); - Assert.Equal(seed.Take(count), builder.ToArray()); - } - } - - [Theory] - [MemberData(nameof(EnumerableData))] - public void AddRange(IEnumerable seed) - { - var builder = new LargeArrayBuilder(); - - // Call AddRange multiple times and verify contents w/ each iteration. - for (int i = 1; i <= 10; i++) - { - builder.AddRange(seed); - - IEnumerable expected = Enumerable.Repeat(seed, i).SelectMany(s => s); - Assert.Equal(expected, builder.ToArray()); - } - } - - [Theory] - [MemberData(nameof(CopyToData))] - public void CopyTo(IEnumerable seed, int index, int count) - { - var array = new T[seed.Count()]; - - var builder = new LargeArrayBuilder(); - builder.AddRange(seed); - builder.CopyTo(array, index, count); - - // Ensure we don't copy out of bounds by verifying contents outside the copy area, too. - IEnumerable prefix = array.Take(index); - IEnumerable suffix = array.Skip(index + count); - IEnumerable actual = array.Skip(index).Take(count); - - Assert.Equal(Enumerable.Repeat(default(T), index), prefix); - Assert.Equal(Enumerable.Repeat(default(T), array.Length - index - count), suffix); - Assert.Equal(seed.Take(count), actual); - } - - public static TheoryData> EnumerableData() - { - var data = new TheoryData>(); - - foreach (int count in Counts) - { - data.Add(Enumerable.Repeat(default(T), count)); - - // Test perf: Capture the items into a List here so we - // only enumerate the sequence once. - data.Add(s_generator.GenerateEnumerable(count).ToList()); - } - - return data; - } - - public static TheoryData, int> MaxCapacityData() - { - var data = new TheoryData, int>(); - - IEnumerable> enumerables = EnumerableData().Select(array => array[0]).Cast>(); - - foreach (IEnumerable enumerable in enumerables) - { - int count = enumerable.Count(); - data.Add(enumerable, count); - } - - return data; - } - - public static TheoryData, int, int> CopyToData() - { - var data = new TheoryData, int, int>(); - - IEnumerable> enumerables = EnumerableData().Select(array => array[0]).Cast>(); - - foreach (IEnumerable enumerable in enumerables) - { - int count = enumerable.Count(); - data.Add(enumerable, 0, count); - - // We want to make sure CopyTo works with different indices/counts too. - data.Add(enumerable, 0, count / 2); - data.Add(enumerable, count / 2, count / 2); - data.Add(enumerable, count / 4, count / 2); - } - - return data; - } - - private static IEnumerable Counts - { - get - { - for (int i = 0; i < 6; i++) - { - int powerOfTwo = 1 << i; - - // Return numbers of the form 2^N - 1, 2^N and 2^N + 1 - // This should cover most of the interesting cases - yield return powerOfTwo - 1; - yield return powerOfTwo; - yield return powerOfTwo + 1; - } - } - } - } - - public class LargeArrayBuilderTestsInt32 : LargeArrayBuilderTests - { - public sealed class Generator : IGenerator - { - public int Generate(int seed) => seed; - } - } - - public class LargeArrayBuilderTestsString : LargeArrayBuilderTests - { - public sealed class Generator : IGenerator - { - public string Generate(int seed) => seed.ToString(); - } - } -} diff --git a/src/libraries/System.Linq/System.Linq.sln b/src/libraries/System.Linq/System.Linq.sln index b6b2290abd290c..5f3c4a80c22450 100644 --- a/src/libraries/System.Linq/System.Linq.sln +++ b/src/libraries/System.Linq/System.Linq.sln @@ -1,4 +1,8 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34431.11 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\Common\tests\TestUtilities\TestUtilities.csproj", "{AF1B1B01-A4EC-45F4-AE51-CC1FA7892181}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Collections", "..\System.Collections\ref\System.Collections.csproj", "{3A8560D8-0E79-4BDE-802A-C96C7FE98258}" @@ -35,11 +39,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E6102BFA-080 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{E61195C4-72B4-47A3-AC98-1F896A0C770F}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "tools\gen", "{84E98F7C-FA2B-4048-AB7C-9FCDEA9CD37E}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{84E98F7C-FA2B-4048-AB7C-9FCDEA9CD37E}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "tools\src", "{8CA90AB2-58B9-45E7-A684-EDB60C6924B0}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8CA90AB2-58B9-45E7-A684-EDB60C6924B0}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "tools\ref", "{7C5B49B9-F7D9-41FB-A8FA-94328BDDCCD1}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{7C5B49B9-F7D9-41FB-A8FA-94328BDDCCD1}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{0ADC596A-5B2E-4E5F-B5B5-DEB65A6C7E9D}" EndProject @@ -111,24 +115,28 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {AF1B1B01-A4EC-45F4-AE51-CC1FA7892181} = {E291F4BF-7B8B-45AD-88F5-FB8B8380C126} - {80A4051B-4A36-4A8B-BA43-A5AB8AA959F3} = {E291F4BF-7B8B-45AD-88F5-FB8B8380C126} {3A8560D8-0E79-4BDE-802A-C96C7FE98258} = {0BCB262A-FC13-4A48-BB0B-9FA293594701} {7E4C1F09-B4F2-470E-9E7B-2C386E93D657} = {0BCB262A-FC13-4A48-BB0B-9FA293594701} - {D3160C37-FC48-4907-8F4A-F584ED12B275} = {0BCB262A-FC13-4A48-BB0B-9FA293594701} {14B966BB-CE23-4432-ADBB-89974389AC1D} = {E6102BFA-0803-4AB7-8E91-C4D3B42AFA20} + {80A4051B-4A36-4A8B-BA43-A5AB8AA959F3} = {E291F4BF-7B8B-45AD-88F5-FB8B8380C126} {9A13A12F-C924-43AF-94AF-6F1B33582D27} = {E61195C4-72B4-47A3-AC98-1F896A0C770F} {4BEC631E-B5FD-453F-82A0-C95C461798EA} = {E61195C4-72B4-47A3-AC98-1F896A0C770F} {C8F0459C-15D5-4624-8CE4-E93ADF96A28C} = {E61195C4-72B4-47A3-AC98-1F896A0C770F} + {D3160C37-FC48-4907-8F4A-F584ED12B275} = {0BCB262A-FC13-4A48-BB0B-9FA293594701} {E0CA3ED5-EE6C-4F7C-BCE7-EFB1D64A9CD1} = {84E98F7C-FA2B-4048-AB7C-9FCDEA9CD37E} {3EFB74E7-616A-48C1-B43B-3F89AA5013E6} = {84E98F7C-FA2B-4048-AB7C-9FCDEA9CD37E} - {84E98F7C-FA2B-4048-AB7C-9FCDEA9CD37E} = {0ADC596A-5B2E-4E5F-B5B5-DEB65A6C7E9D} {28ABC524-ACEE-4183-A64A-49E3DC830595} = {8CA90AB2-58B9-45E7-A684-EDB60C6924B0} {721DB3D9-8221-424E-BE29-084CDD20D26E} = {8CA90AB2-58B9-45E7-A684-EDB60C6924B0} - {8CA90AB2-58B9-45E7-A684-EDB60C6924B0} = {0ADC596A-5B2E-4E5F-B5B5-DEB65A6C7E9D} {E19B8772-2DBD-4274-8190-F3CC0242A1C0} = {7C5B49B9-F7D9-41FB-A8FA-94328BDDCCD1} + {84E98F7C-FA2B-4048-AB7C-9FCDEA9CD37E} = {0ADC596A-5B2E-4E5F-B5B5-DEB65A6C7E9D} + {8CA90AB2-58B9-45E7-A684-EDB60C6924B0} = {0ADC596A-5B2E-4E5F-B5B5-DEB65A6C7E9D} {7C5B49B9-F7D9-41FB-A8FA-94328BDDCCD1} = {0ADC596A-5B2E-4E5F-B5B5-DEB65A6C7E9D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A4970D79-BF1C-4343-9070-B409DBB69F93} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems*{3efb74e7-616a-48c1-b43b-3f89aa5013e6}*SharedItemsImports = 5 + ..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems*{721db3d9-8221-424e-be29-084cdd20d26e}*SharedItemsImports = 5 + EndGlobalSection EndGlobal diff --git a/src/libraries/System.Linq/src/System.Linq.csproj b/src/libraries/System.Linq/src/System.Linq.csproj index 6e810da322b40f..2fc4153be6890a 100644 --- a/src/libraries/System.Linq/src/System.Linq.csproj +++ b/src/libraries/System.Linq/src/System.Linq.csproj @@ -14,8 +14,6 @@ - @@ -36,26 +34,13 @@ - - - - - - - @@ -88,6 +73,7 @@ + diff --git a/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs index caf83bee1fcaac..80ee23998603fa 100644 --- a/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs @@ -22,22 +22,45 @@ private sealed partial class AppendPrepend1Iterator private TSource[] LazyToArray() { Debug.Assert(GetCount(onlyIfCheap: true) == -1); + TSource[] result; - LargeArrayBuilder builder = new(); - - if (!_appending) + if (_source is ICollection c) { - builder.SlowAdd(_item); + // Allocate an array of the exact size needed. We have a collection + // with an additional item either before it or after it; copy them + // all to the new array appropriately. + result = new TSource[c.Count + 1]; + if (_appending) + { + c.CopyTo(result, 0); + result[^1] = _item; + } + else + { + c.CopyTo(result, 1); + result[0] = _item; + } } - - builder.AddRange(_source); - - if (_appending) + else { - builder.SlowAdd(_item); + SegmentedArrayBuilder.ScratchBuffer scratch = default; + SegmentedArrayBuilder builder = new(scratch); + if (_appending) + { + builder.AddNonICollectionRange(_source); + builder.Add(_item); + } + else + { + builder.Add(_item); + builder.AddNonICollectionRange(_source); + } + + result = builder.ToArray(); + builder.Dispose(); } - return builder.ToArray(); + return result; } public override TSource[] ToArray() @@ -60,11 +83,21 @@ public override TSource[] ToArray() index = 1; } - EnumerableHelpers.Copy(_source, array, index, count - 1); + if (_source is ICollection collection) + { + collection.CopyTo(array, index); + } + else + { + foreach (TSource item in _source) + { + array[index++] = item; + } + } if (_appending) { - array[array.Length - 1] = _item; + array[^1] = _item; } return array; @@ -113,26 +146,35 @@ private TSource[] LazyToArray() { Debug.Assert(GetCount(onlyIfCheap: true) == -1); - SparseArrayBuilder builder = new(); - - if (_prepended != null) + if (_source is ICollection c) { - builder.Reserve(_prependCount); - } + var result = new TSource[checked(_prependCount + c.Count + _appendCount)]; - builder.AddRange(_source); + _prepended?.Fill(result); + c.CopyTo(result, _prependCount); + _appended?.FillReversed(result); - if (_appended != null) - { - builder.Reserve(_appendCount); + return result; } + else + { + // Create the new builder with the prepended content and source content. Then + // build the resulting array with enough space to also hold any appended content, + // and write the appended content directly into the resulting array. + SegmentedArrayBuilder.ScratchBuffer scratch = default; + SegmentedArrayBuilder builder = new(scratch); + for (SingleLinkedNode? node = _prepended; node is not null; node = node.Linked) + { + builder.Add(node.Item); + } + builder.AddNonICollectionRange(_source); - TSource[] array = builder.ToArray(); - - _prepended?.Fill(array); - _appended?.FillReversed(array); + TSource[] result = builder.ToArray(_appendCount); + builder.Dispose(); - return array; + _appended?.FillReversed(result); + return result; + } } public override TSource[] ToArray() diff --git a/src/libraries/System.Linq/src/System/Linq/Average.cs b/src/libraries/System.Linq/src/System/Linq/Average.cs index e71b3558d71493..df0f34cf098eb6 100644 --- a/src/libraries/System.Linq/src/System/Linq/Average.cs +++ b/src/libraries/System.Linq/src/System/Linq/Average.cs @@ -10,6 +10,11 @@ public static partial class Enumerable { public static double Average(this IEnumerable source) { + if (source is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + if (source.TryGetSpan(out ReadOnlySpan span)) { // Int32 is special-cased separately from the rest of the types as it can be vectorized: @@ -79,6 +84,11 @@ private static TResult Average(this IEnumerable< where TAccumulator : struct, INumber where TResult : struct, INumber { + if (source is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + if (source.TryGetSpan(out ReadOnlySpan span)) { if (span.IsEmpty) diff --git a/src/libraries/System.Linq/src/System/Linq/Buffer.cs b/src/libraries/System.Linq/src/System/Linq/Buffer.cs deleted file mode 100644 index 1821a87b100d52..00000000000000 --- a/src/libraries/System.Linq/src/System/Linq/Buffer.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; - -namespace System.Linq -{ - /// - /// A buffer into which the contents of an can be stored. - /// - /// The type of the buffer's elements. - internal readonly struct Buffer - { - /// - /// The stored items. - /// - internal readonly TElement[] _items; - - /// - /// The number of stored items. - /// - internal readonly int _count; - - /// - /// Fully enumerates the provided enumerable and stores its items into an array. - /// - /// The enumerable to be store. - internal Buffer(IEnumerable source) - { - if (source is IIListProvider iterator) - { - TElement[] array = iterator.ToArray(); - _items = array; - _count = array.Length; - } - else - { - _items = EnumerableHelpers.ToArray(source, out _count); - } - } - } -} diff --git a/src/libraries/System.Linq/src/System/Linq/Concat.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Concat.SpeedOpt.cs index 1df7809baa65c6..d3e67aac26e166 100644 --- a/src/libraries/System.Linq/src/System/Linq/Concat.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Concat.SpeedOpt.cs @@ -38,27 +38,56 @@ public override int GetCount(bool onlyIfCheap) public override TSource[] ToArray() { - SparseArrayBuilder builder = new(); + ICollection? firstCollection = _first as ICollection; + ICollection? secondCollection = _second as ICollection; - bool reservedFirst = builder.ReserveOrAdd(_first); - bool reservedSecond = builder.ReserveOrAdd(_second); + if (firstCollection is not null && secondCollection is not null) + { + // Both sources are ICollection, so we know their sizes and can just copy them. + int firstCount = firstCollection.Count; + TSource[] result = new TSource[checked(firstCount + secondCollection.Count)]; - TSource[] array = builder.ToArray(); + firstCollection.CopyTo(result, 0); + secondCollection.CopyTo(result, firstCount); - if (reservedFirst) - { - Marker marker = builder.Markers.First(); - Debug.Assert(marker.Index == 0); - EnumerableHelpers.Copy(_first, array, 0, marker.Count); + return result; } - - if (reservedSecond) + else { - Marker marker = builder.Markers.Last(); - EnumerableHelpers.Copy(_second, array, marker.Index, marker.Count); - } + // We don't know the sizes of at least one if not both sources, so we need a builder. + // If we don't know the sizes of both, we'll just append each into the builder and + // use the builder to create the overall array. If we know the size of one, we'll + // only buffer the other. + SegmentedArrayBuilder.ScratchBuffer scratch = default; + SegmentedArrayBuilder builder = new(scratch); + TSource[] result; + + if (firstCollection is not null) + { + int firstCount = firstCollection.Count; + builder.AddNonICollectionRange(_second); + result = new TSource[checked(firstCount + builder.Count)]; + firstCollection.CopyTo(result, 0); + builder.ToSpan(result.AsSpan(firstCount)); + } + else if (secondCollection is not null) + { + int secondCount = secondCollection.Count; + builder.AddNonICollectionRange(_first); + result = new TSource[checked(builder.Count + secondCount)]; + builder.ToSpan(result); + secondCollection.CopyTo(result, result.Length - secondCount); + } + else + { + builder.AddNonICollectionRange(_first); + builder.AddNonICollectionRange(_second); + result = builder.ToArray(); + } - return array; + builder.Dispose(); + return result; + } } } @@ -100,10 +129,12 @@ public override int GetCount(bool onlyIfCheap) private TSource[] LazyToArray() { + // All of the sources being ICollection is handled by PreallocatingToArray, so if we're here, + // at least one source isn't an ICollection. Debug.Assert(!_hasOnlyCollections); - SparseArrayBuilder builder = new(); - ArrayBuilder deferredCopies = default; + SegmentedArrayBuilder.ScratchBuffer scratch = default; + SegmentedArrayBuilder builder = new(scratch); for (int i = 0; ; i++) { @@ -111,30 +142,19 @@ private TSource[] LazyToArray() // quadratic behavior, because we need to add the sources in order. // On the bright side, the bottleneck will usually be iterating, buffering, and copying // each of the enumerables, so this shouldn't be a noticeable perf hit for most scenarios. - IEnumerable? source = GetEnumerable(i); if (source == null) { break; } - if (builder.ReserveOrAdd(source)) - { - deferredCopies.Add(i); - } + builder.AddRange(source); } - TSource[] array = builder.ToArray(); + TSource[] result = builder.ToArray(); + builder.Dispose(); - ArrayBuilder markers = builder.Markers; - for (int i = 0; i < markers.Count; i++) - { - Marker marker = markers[i]; - IEnumerable source = GetEnumerable(deferredCopies[i])!; - EnumerableHelpers.Copy(source, array, marker.Index, marker.Count); - } - - return array; + return result; } private TSource[] PreallocatingToArray() diff --git a/src/libraries/System.Linq/src/System/Linq/Enumerable.cs b/src/libraries/System.Linq/src/System/Linq/Enumerable.cs index 887a3800683124..fbdf9a12a28b65 100644 --- a/src/libraries/System.Linq/src/System/Linq/Enumerable.cs +++ b/src/libraries/System.Linq/src/System/Linq/Enumerable.cs @@ -37,18 +37,8 @@ internal static Span SetCountAndGetSpan(List list, int count) /// Validates that source is not null and then tries to extract a span from the source. [MethodImpl(MethodImplOptions.AggressiveInlining)] // fast type checks that don't add a lot of overhead - private static bool TryGetSpan(this IEnumerable source, out ReadOnlySpan span) - // This constraint isn't required, but the overheads involved here can be more substantial when TSource - // is a reference type and generic implementations are shared. So for now we're protecting ourselves - // and forcing a conscious choice to remove this in the future, at which point it should be paired with - // sufficient performance testing. - where TSource : struct + internal static bool TryGetSpan(this IEnumerable source, out ReadOnlySpan span) { - if (source is null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); - } - // Use `GetType() == typeof(...)` rather than `is` to avoid cast helpers. This is measurably cheaper // but does mean we could end up missing some rare cases where we could get a span but don't (e.g. a uint[] // masquerading as an int[]). That's an acceptable tradeoff. The Unsafe usage is only after we've diff --git a/src/libraries/System.Linq/src/System/Linq/Max.cs b/src/libraries/System.Linq/src/System/Linq/Max.cs index d3189720456d73..d0da2f7bfd7415 100644 --- a/src/libraries/System.Linq/src/System/Linq/Max.cs +++ b/src/libraries/System.Linq/src/System/Linq/Max.cs @@ -99,6 +99,11 @@ private static T MaxFloat(this IEnumerable source) where T : struct, IFloa { T value; + if (source is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + if (source.TryGetSpan(out ReadOnlySpan span)) { if (span.IsEmpty) @@ -218,6 +223,11 @@ public static decimal Max(this IEnumerable source) { decimal value; + if (source is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + if (source.TryGetSpan(out ReadOnlySpan span)) { if (span.IsEmpty) diff --git a/src/libraries/System.Linq/src/System/Linq/MaxMin.cs b/src/libraries/System.Linq/src/System/Linq/MaxMin.cs index 759c4ce65627dd..f8af828bc95087 100644 --- a/src/libraries/System.Linq/src/System/Linq/MaxMin.cs +++ b/src/libraries/System.Linq/src/System/Linq/MaxMin.cs @@ -25,6 +25,11 @@ private static T MinMaxInteger(this IEnumerable source) { T value; + if (source is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + if (source.TryGetSpan(out ReadOnlySpan span)) { if (span.IsEmpty) diff --git a/src/libraries/System.Linq/src/System/Linq/Min.cs b/src/libraries/System.Linq/src/System/Linq/Min.cs index 632d643655174a..3a0f2130d966b3 100644 --- a/src/libraries/System.Linq/src/System/Linq/Min.cs +++ b/src/libraries/System.Linq/src/System/Linq/Min.cs @@ -81,6 +81,11 @@ private static T MinFloat(this IEnumerable source) where T : struct, IFloa { T value; + if (source is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + if (source.TryGetSpan(out ReadOnlySpan span)) { if (span.IsEmpty) @@ -197,6 +202,11 @@ public static decimal Min(this IEnumerable source) { decimal value; + if (source is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + if (source.TryGetSpan(out ReadOnlySpan span)) { if (span.IsEmpty) diff --git a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs index 1e6f367bb37a85..b7a45d95a1a1fb 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs @@ -11,38 +11,36 @@ internal abstract partial class OrderedEnumerable : IPartition buffer = new Buffer(_source); - - int count = buffer._count; - if (count == 0) + TElement[] buffer = _source.ToArray(); + if (buffer.Length == 0) { - return buffer._items; + return buffer; } - TElement[] array = new TElement[count]; + TElement[] array = new TElement[buffer.Length]; Fill(buffer, array); return array; } public virtual List ToList() { - Buffer buffer = new Buffer(_source); - int count = buffer._count; - List list = new List(count); - if (count > 0) + TElement[] buffer = _source.ToArray(); + + List list = new(); + if (buffer.Length > 0) { - Fill(buffer, Enumerable.SetCountAndGetSpan(list, count)); + Fill(buffer, Enumerable.SetCountAndGetSpan(list, buffer.Length)); } return list; } - private void Fill(Buffer buffer, Span destination) + private void Fill(TElement[] buffer, Span destination) { int[] map = SortedMap(buffer); for (int i = 0; i < destination.Length; i++) { - destination[i] = buffer._items[map[i]]; + destination[i] = buffer[map[i]]; } } @@ -58,21 +56,20 @@ public int GetCount(bool onlyIfCheap) internal TElement[] ToArray(int minIdx, int maxIdx) { - Buffer buffer = new Buffer(_source); - int count = buffer._count; - if (count <= minIdx) + TElement[] buffer = _source.ToArray(); + if (buffer.Length <= minIdx) { return []; } - if (count <= maxIdx) + if (buffer.Length <= maxIdx) { - maxIdx = count - 1; + maxIdx = buffer.Length - 1; } if (minIdx == maxIdx) { - return [GetEnumerableSorter().ElementAt(buffer._items, count, minIdx)]; + return [GetEnumerableSorter().ElementAt(buffer, buffer.Length, minIdx)]; } TElement[] array = new TElement[maxIdx - minIdx + 1]; @@ -84,35 +81,34 @@ internal TElement[] ToArray(int minIdx, int maxIdx) internal List ToList(int minIdx, int maxIdx) { - Buffer buffer = new Buffer(_source); - int count = buffer._count; - if (count <= minIdx) + TElement[] buffer = _source.ToArray(); + if (buffer.Length <= minIdx) { return new List(); } - if (count <= maxIdx) + if (buffer.Length <= maxIdx) { - maxIdx = count - 1; + maxIdx = buffer.Length - 1; } if (minIdx == maxIdx) { - return new List(1) { GetEnumerableSorter().ElementAt(buffer._items, count, minIdx) }; + return new List(1) { GetEnumerableSorter().ElementAt(buffer, buffer.Length, minIdx) }; } - List list = new List(maxIdx - minIdx + 1); + List list = new(); Fill(minIdx, maxIdx, buffer, Enumerable.SetCountAndGetSpan(list, maxIdx - minIdx + 1)); return list; } - private void Fill(int minIdx, int maxIdx, Buffer buffer, Span destination) + private void Fill(int minIdx, int maxIdx, TElement[] buffer, Span destination) { int[] map = SortedMap(buffer, minIdx, maxIdx); int idx = 0; while (minIdx <= maxIdx) { - destination[idx] = buffer._items[map[minIdx]]; + destination[idx] = buffer[map[minIdx]]; ++idx; ++minIdx; } @@ -147,12 +143,11 @@ internal int GetCount(int minIdx, int maxIdx, bool onlyIfCheap) if (index > 0) { - Buffer buffer = new Buffer(_source); - int count = buffer._count; - if (index < count) + TElement[] buffer = _source.ToArray(); + if (index < buffer.Length) { found = true; - return GetEnumerableSorter().ElementAt(buffer._items, count, index); + return GetEnumerableSorter().ElementAt(buffer, buffer.Length, index); } } @@ -216,29 +211,30 @@ internal int GetCount(int minIdx, int maxIdx, bool onlyIfCheap) public TElement? TryGetLast(int minIdx, int maxIdx, out bool found) { - Buffer buffer = new Buffer(_source); - int count = buffer._count; - if (minIdx >= count) + TElement[] buffer = _source.ToArray(); + if (minIdx < buffer.Length) { - found = false; - return default; + found = true; + return (maxIdx < buffer.Length - 1) ? + GetEnumerableSorter().ElementAt(buffer, buffer.Length, maxIdx) : + Last(buffer); } - found = true; - return (maxIdx < count - 1) ? GetEnumerableSorter().ElementAt(buffer._items, count, maxIdx) : Last(buffer); + found = false; + return default; } - private TElement Last(Buffer buffer) + private TElement Last(TElement[] items) { CachingComparer comparer = GetComparer(); - TElement[] items = buffer._items; - int count = buffer._count; + TElement value = items[0]; comparer.SetElement(value); - for (int i = 1; i != count; ++i) + + for (int i = 1; i < items.Length; ++i) { TElement x = items[i]; - if (comparer.Compare(x, false) >= 0) + if (comparer.Compare(x, cacheLower: false) >= 0) { value = x; } diff --git a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs index 1bcdb6b2b780d1..2828d765fdfaea 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs @@ -13,28 +13,28 @@ internal abstract partial class OrderedEnumerable : IOrderedEnumerable protected OrderedEnumerable(IEnumerable source) => _source = source; - private int[] SortedMap(Buffer buffer) => GetEnumerableSorter().Sort(buffer._items, buffer._count); + private int[] SortedMap(TElement[] buffer) => GetEnumerableSorter().Sort(buffer, buffer.Length); - private int[] SortedMap(Buffer buffer, int minIdx, int maxIdx) => - GetEnumerableSorter().Sort(buffer._items, buffer._count, minIdx, maxIdx); + private int[] SortedMap(TElement[] buffer, int minIdx, int maxIdx) => + GetEnumerableSorter().Sort(buffer, buffer.Length, minIdx, maxIdx); public virtual IEnumerator GetEnumerator() { - Buffer buffer = new Buffer(_source); - if (buffer._count > 0) + TElement[] buffer = _source.ToArray(); + if (buffer.Length > 0) { int[] map = SortedMap(buffer); - for (int i = 0; i < buffer._count; i++) + for (int i = 0; i < buffer.Length; i++) { - yield return buffer._items[map[i]]; + yield return buffer[map[i]]; } } } internal IEnumerator GetEnumerator(int minIdx, int maxIdx) { - Buffer buffer = new Buffer(_source); - int count = buffer._count; + TElement[] buffer = _source.ToArray(); + int count = buffer.Length; if (count > minIdx) { if (count <= maxIdx) @@ -44,14 +44,14 @@ internal IEnumerator GetEnumerator(int minIdx, int maxIdx) if (minIdx == maxIdx) { - yield return GetEnumerableSorter().ElementAt(buffer._items, count, minIdx); + yield return GetEnumerableSorter().ElementAt(buffer, count, minIdx); } else { int[] map = SortedMap(buffer, minIdx, maxIdx); while (minIdx <= maxIdx) { - yield return buffer._items[map[minIdx]]; + yield return buffer[map[minIdx]]; ++minIdx; } } @@ -184,13 +184,13 @@ internal override EnumerableSorter GetEnumerableSorter(EnumerableSorte public override IEnumerator GetEnumerator() { - var buffer = new Buffer(_source); - if (buffer._count > 0) + TElement[] buffer = _source.ToArray(); + if (buffer.Length > 0) { - Sort(buffer._items.AsSpan(0, buffer._count), _descending); - for (int i = 0; i < buffer._count; i++) + Sort(buffer, _descending); + for (int i = 0; i < buffer.Length; i++) { - yield return buffer._items[i]; + yield return buffer[i]; } } } diff --git a/src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs index dbcd20974a3066..92f71cb058d478 100644 --- a/src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs @@ -511,9 +511,8 @@ public TSource[] ToArray() int remaining = Limit - 1; // Max number of items left, not counting the current element. int comparand = HasLimit ? 0 : int.MinValue; // If we don't have an upper bound, have the comparison always return true. - int maxCapacity = HasLimit ? Limit : int.MaxValue; - var builder = new LargeArrayBuilder(maxCapacity); - + SegmentedArrayBuilder.ScratchBuffer scratch = default; + SegmentedArrayBuilder builder = new(scratch); do { remaining--; @@ -521,7 +520,10 @@ public TSource[] ToArray() } while (remaining >= comparand && en.MoveNext()); - return builder.ToArray(); + TSource[] result = builder.ToArray(); + builder.Dispose(); + + return result; } } diff --git a/src/libraries/System.Linq/src/System/Linq/Reverse.cs b/src/libraries/System.Linq/src/System/Linq/Reverse.cs index 674b30bbc49c94..2a02115daca12a 100644 --- a/src/libraries/System.Linq/src/System/Linq/Reverse.cs +++ b/src/libraries/System.Linq/src/System/Linq/Reverse.cs @@ -59,9 +59,9 @@ public override bool MoveNext() // Iteration has just started. Capture the source into an array and set _state to 2 + the count. // Having an extra field for the count would be more readable, but we save it into _state with a // bias instead to minimize field size of the iterator. - Buffer buffer = new Buffer(_source); - _buffer = buffer._items; - _state = buffer._count + 2; + TSource[] buffer = _source.ToArray(); + _buffer = buffer; + _state = buffer.Length + 2; goto default; default: // At this stage, _state starts from 2 + the count. _state - 3 represents the current index into the diff --git a/src/libraries/System.Linq/src/System/Linq/SegmentedArrayBuilder.cs b/src/libraries/System.Linq/src/System/Linq/SegmentedArrayBuilder.cs new file mode 100644 index 00000000000000..ef85efa11ee48d --- /dev/null +++ b/src/libraries/System.Linq/src/System/Linq/SegmentedArrayBuilder.cs @@ -0,0 +1,313 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace System.Collections.Generic +{ + /// Provides a helper for efficiently building arrays and lists. + /// This is implemented as an inline array of rented arrays. + /// Specifies the element type of the collection being built. + internal ref struct SegmentedArrayBuilder + { + /// The size to use for the first segment that's stack allocated by the caller. + /// + /// This value needs to be small enough that we don't need to be overly concerned about really large + /// value types. It's not unreasonable for a method to say it has 8 locals of a T, and that's effectively + /// what this is. + /// + private const int ScratchBufferSize = 8; + /// Minimum size to request renting from the pool. + private const int MinimumRentSize = 16; + + /// The array of segments. + /// is how many of the segments are valid in , not including . + private Arrays _segments; + /// The scratch buffer provided by the caller. + /// This is treated as the initial segment, before anything in . + private Span _firstSegment; + /// The current span. This points either to or to [ - 1]. + private Span _currentSegment; + /// The count of segments in that are valid. + /// All but the last are known to be fully populated. + private int _segmentsCount; + /// The total number of elements in all but the current/last segment. + private int _countInFinishedSegments; + /// The number of elements in the current/last segment. + private int _countInCurrentSegment; + + /// Initialize the builder. + /// A buffer that can be used as part of the builder. + public SegmentedArrayBuilder(Span scratchBuffer) + { + _currentSegment = _firstSegment = scratchBuffer; + } + + /// Clean up the resources used by the builder. + public void Dispose() + { + int segmentsCount = _segmentsCount; + if (segmentsCount != 0) + { + ReadOnlySpan segments = _segments; + + // We need to return all rented arrays to the pool, and if the arrays contain any references, + // we want to clear them first so that the pool doesn't artificially root contained objects. + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + // Return all but the last segment. All of these are full and need to be entirely cleared. + foreach (T[] segment in segments.Slice(0, segmentsCount - 1)) + { + ArrayPool.Shared.Return(segment, clearArray: true); + } + + // For the last segment, we can clear only what we know was used. + T[] currentSegment = segments[segmentsCount - 1]; + Array.Clear(currentSegment, 0, _countInCurrentSegment); + ArrayPool.Shared.Return(currentSegment); + } + else + { + // Return every rented array without clearing. + foreach (T[] segment in segments.Slice(0, segmentsCount)) + { + ArrayPool.Shared.Return(segment); + } + } + } + } + + /// Gets the number of elements in the builder. + public readonly int Count => checked(_countInFinishedSegments + _countInCurrentSegment); + + /// Adds an item into the builder. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + Span currentSegment = _currentSegment; + int countInCurrentSegment = _countInCurrentSegment; + if ((uint)countInCurrentSegment < (uint)currentSegment.Length) + { + currentSegment[countInCurrentSegment] = item; + _countInCurrentSegment++; + } + else + { + AddSlow(item); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void AddSlow(T item) + { + Expand(); + _currentSegment[0] = item; + _countInCurrentSegment = 1; + } + + /// Adds a collection of items into the builder. + public void AddRange(IEnumerable source) + { + if (source is ICollection collection) + { + int collectionCount = collection.Count; + + // If the source is empty, there's nothing to add. + if (collectionCount == 0) + { + return; + } + + // If the source is something from which we can get a span, e.g. a T[] or a List, + // we can do one or two copies to handle copying everything, even if we need to split + // across segments. + if (Enumerable.TryGetSpan(source, out ReadOnlySpan sourceSpan)) + { + int availableSpaceInCurrentSpan = _currentSegment.Length - _countInCurrentSegment; + ReadOnlySpan sourceSlice = sourceSpan.Slice(0, Math.Min(availableSpaceInCurrentSpan, sourceSpan.Length)); + sourceSlice.CopyTo(_currentSegment.Slice(_countInCurrentSegment)); + _countInCurrentSegment += sourceSlice.Length; + sourceSlice = sourceSpan.Slice(sourceSlice.Length); + + if (!sourceSlice.IsEmpty) + { + Expand(sourceSlice.Length); + sourceSlice.CopyTo(_currentSegment); + _countInCurrentSegment = sourceSlice.Length; + } + + return; + } + + // Otherwise, since we have an ICollection, we'd like to use ICollection.CopyTo, but it + // requires targeting an array, so we can't use it if we're using a scratch buffer. + bool currentSegmentIsScratchBufferWithRemainingSpace = _segmentsCount == 0 && _countInCurrentSegment < _currentSegment.Length; + if (!currentSegmentIsScratchBufferWithRemainingSpace) + { + // It also only works if we can copy the whole collection in one go, which + // means we need enough space in the current segment to hold the whole collection + // or we need to be at the end of the current segment so we can allocate a + // new one without leaving any holes. + int remainingSpaceInCurrentSegment = _currentSegment.Length - _countInCurrentSegment; + + // If there's no space remaining in the current segment, we can just expand + // into a new segment that we ensure is large enough. + if (remainingSpaceInCurrentSegment == 0) + { + Expand(collectionCount); + collection.CopyTo(_segments[_segmentsCount - 1], 0); + _countInCurrentSegment = collectionCount; + return; + } + + // If there's enough space remaining in the current segment, we can also just copy into it. + if (collectionCount <= remainingSpaceInCurrentSegment) + { + collection.CopyTo(_segments[_segmentsCount - 1], _countInCurrentSegment); + _countInCurrentSegment += collectionCount; + return; + } + + // Otherwise, we're forced to fall back to enumeration. + } + } + + // Fall back to enumerating and adding each element individually. + AddNonICollectionRangeInlined(source); + } + + /// Adds a collection of items into the builder. + /// + /// The implementation assumes the caller has already ruled out the source being + /// and ICollection and thus doesn't bother checking to see if it is. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + public void AddNonICollectionRange(IEnumerable source) => + AddNonICollectionRangeInlined(source); + + /// Adds a collection of items into the builder. + /// + /// The implementation assumes the caller has already ruled out the source being + /// and ICollection and thus doesn't bother checking to see if it is. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddNonICollectionRangeInlined(IEnumerable source) + { + Span currentSegment = _currentSegment; + int countInCurrentSegment = _countInCurrentSegment; + + foreach (T item in source) + { + if ((uint)countInCurrentSegment < (uint)currentSegment.Length) + { + currentSegment[countInCurrentSegment] = item; + countInCurrentSegment++; + } + else + { + Expand(); + currentSegment = _currentSegment; + currentSegment[0] = item; + countInCurrentSegment = 1; + } + } + + _countInCurrentSegment = countInCurrentSegment; + } + + /// Creates an array containing all of the elements in the builder. + /// The number of extra elements of room to allocate in the resulting array. + public T[] ToArray(int additionalLength = 0) + { + T[] result = []; + + int count = checked(Count + additionalLength); + if (count != 0) + { + result = GC.AllocateUninitializedArray(count); + ToSpan(result); + } + + return result; + } + + /// Populates the destination span with all of the elements in the builder. + /// The destination span. + public void ToSpan(Span destination) + { + int segmentsCount = _segmentsCount; + if (segmentsCount != 0) + { + // Copy the first segment + ReadOnlySpan firstSegment = _firstSegment; + firstSegment.CopyTo(destination); + destination = destination.Slice(firstSegment.Length); + + // Copy the 0..N-1 segments + segmentsCount--; + foreach (T[] arr in ((ReadOnlySpan)_segments).Slice(0, segmentsCount)) + { + ReadOnlySpan segment = arr; + segment.CopyTo(destination); + destination = destination.Slice(segment.Length); + } + } + + // Copy the last segment + _currentSegment.Slice(0, _countInCurrentSegment).CopyTo(destination); + } + + /// Appends a new segment onto the builder. + /// The minimum amount of space to allocate in a new segment being appended. + private void Expand(int minimumRequired = MinimumRentSize) + { + if (minimumRequired < MinimumRentSize) + { + minimumRequired = MinimumRentSize; + } + + // Update our count of the number of elements in the arrays. + // If we know we're exceeding the maximum allowed array length, throw. + int currentSegmentLength = _currentSegment.Length; + checked { _countInFinishedSegments += currentSegmentLength; } + if (_countInFinishedSegments > Array.MaxLength) + { + throw new OutOfMemoryException(); + } + + // Use a typical doubling algorithm to decide the length of the next array + // and allocate it. We want to double the current array length, but if the + // minimum required is larger than that, use the minimum required. And if + // doubling would result in going above the max array length, only use the + // max array length, as List does. + int newSegmentLength = (int)Math.Min(Math.Max(minimumRequired, currentSegmentLength * 2L), Array.MaxLength); + _currentSegment = _segments[_segmentsCount] = ArrayPool.Shared.Rent(newSegmentLength); + _segmentsCount++; + } + +#pragma warning disable IDE0044 // Add readonly modifier +#pragma warning disable IDE0051 // Remove unused private members + /// A struct to hold all of the T[]s that compose the full result set. + /// + /// Starting at the minimum size of , and with a minimum of doubling + /// on every growth, this is large enough to hold the maximum number arrays that could result + /// until the total length has exceeded Array.MaxLength. + /// + [InlineArray(27)] + private struct Arrays + { + private T[] _values; + } + + /// Provides a stack-allocatable buffer for use as an argument to the builder. + [InlineArray(ScratchBufferSize)] + public struct ScratchBuffer + { + private T _item; + } +#pragma warning restore IDE0051 +#pragma warning restore IDE0044 + } +} diff --git a/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs index c9e8be3f9744f4..f59b6e7b08fbae 100644 --- a/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs @@ -20,23 +20,29 @@ private sealed partial class SelectEnumerableIterator : IIList { public TResult[] ToArray() { - LargeArrayBuilder builder = new(); + SegmentedArrayBuilder.ScratchBuffer scratch = default; + SegmentedArrayBuilder builder = new(scratch); + Func selector = _selector; foreach (TSource item in _source) { - builder.Add(_selector(item)); + builder.Add(selector(item)); } - return builder.ToArray(); + TResult[] result = builder.ToArray(); + builder.Dispose(); + + return result; } public List ToList() { var list = new List(); + Func selector = _selector; foreach (TSource item in _source) { - list.Add(_selector(item)); + list.Add(selector(item)); } return list; @@ -598,13 +604,19 @@ private TResult[] LazyToArray() { Debug.Assert(_source.GetCount(onlyIfCheap: true) == -1); - LargeArrayBuilder builder = new(); + SegmentedArrayBuilder.ScratchBuffer scratch = default; + SegmentedArrayBuilder builder = new(scratch); + Func selector = _selector; foreach (TSource input in _source) { - builder.Add(_selector(input)); + builder.Add(selector(input)); } - return builder.ToArray(); + + TResult[] result = builder.ToArray(); + builder.Dispose(); + + return result; } private TResult[] PreallocatingToArray(int count) diff --git a/src/libraries/System.Linq/src/System/Linq/SelectMany.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/SelectMany.SpeedOpt.cs index b485700db4dd2c..050ae6a4e06b61 100644 --- a/src/libraries/System.Linq/src/System/Linq/SelectMany.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/SelectMany.SpeedOpt.cs @@ -31,39 +31,29 @@ public int GetCount(bool onlyIfCheap) public TResult[] ToArray() { - SparseArrayBuilder builder = new(); - ArrayBuilder> deferredCopies = default; + SegmentedArrayBuilder.ScratchBuffer scratch = default; + SegmentedArrayBuilder builder = new(scratch); - foreach (TSource element in _source) + Func> selector = _selector; + foreach (TSource item in _source) { - IEnumerable enumerable = _selector(element); - - if (builder.ReserveOrAdd(enumerable)) - { - deferredCopies.Add(enumerable); - } + builder.AddRange(selector(item)); } - TResult[] array = builder.ToArray(); - - ArrayBuilder markers = builder.Markers; - for (int i = 0; i < markers.Count; i++) - { - Marker marker = markers[i]; - IEnumerable enumerable = deferredCopies[i]; - EnumerableHelpers.Copy(enumerable, array, marker.Index, marker.Count); - } + TResult[] result = builder.ToArray(); + builder.Dispose(); - return array; + return result; } public List ToList() { var list = new List(); + Func> selector = _selector; foreach (TSource element in _source) { - list.AddRange(_selector(element)); + list.AddRange(selector(element)); } return list; diff --git a/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs b/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs index 051ebe769c1f25..e0bf5e54c51ee0 100644 --- a/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs +++ b/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs @@ -99,7 +99,7 @@ public TSource[] ToArray(int count) /// /// Fills a start of a span with the items of this node's singly-linked list. /// - /// The span to fill. Must be the precise size required. + /// The span to fill. Must be at least the size required. public void Fill(Span span) { int index = 0; diff --git a/src/libraries/System.Linq/src/System/Linq/Sum.cs b/src/libraries/System.Linq/src/System/Linq/Sum.cs index ae670ff3e96876..7eb2dc855dc67b 100644 --- a/src/libraries/System.Linq/src/System/Linq/Sum.cs +++ b/src/libraries/System.Linq/src/System/Linq/Sum.cs @@ -25,6 +25,11 @@ private static TResult Sum(this IEnumerable source) where TSource : struct, INumber where TResult : struct, INumber { + if (source is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + if (source.TryGetSpan(out ReadOnlySpan span)) { return Sum(span); diff --git a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs index 2fd7f475fafbf4..8b2ad651d1d2bd 100644 --- a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs +++ b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs @@ -11,24 +11,54 @@ public static partial class Enumerable { public static TSource[] ToArray(this IEnumerable source) { - if (source == null) + if (source is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } - return source is IIListProvider arrayProvider - ? arrayProvider.ToArray() - : EnumerableHelpers.ToArray(source); + if (source is IIListProvider arrayProvider) + { + return arrayProvider.ToArray(); + } + + if (source is ICollection collection) + { + int count = collection.Count; + if (count != 0) + { + var result = new TSource[count]; + collection.CopyTo(result, 0); + return result; + } + + return []; + } + else + { + SegmentedArrayBuilder.ScratchBuffer scratch = default; + SegmentedArrayBuilder builder = new(scratch); + + builder.AddNonICollectionRange(source); + TSource[] result = builder.ToArray(); + + builder.Dispose(); + return result; + } } public static List ToList(this IEnumerable source) { - if (source == null) + if (source is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } - return source is IIListProvider listProvider ? listProvider.ToList() : new List(source); + if (source is IIListProvider listProvider) + { + return listProvider.ToList(); + } + + return new List(source); } /// @@ -99,12 +129,12 @@ public static Dictionary ToDictionary(this IEnumer public static Dictionary ToDictionary(this IEnumerable source, Func keySelector, IEqualityComparer? comparer) where TKey : notnull { - if (source == null) + if (source is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } - if (keySelector == null) + if (keySelector is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); } @@ -152,17 +182,17 @@ public static Dictionary ToDictionary(t public static Dictionary ToDictionary(this IEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer? comparer) where TKey : notnull { - if (source == null) + if (source is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } - if (keySelector == null) + if (keySelector is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector); } - if (elementSelector == null) + if (elementSelector is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.elementSelector); } @@ -209,7 +239,7 @@ private static Dictionary SpanToDictionary ToHashSet(this IEnumerable source, IEqualityComparer? comparer) { - if (source == null) + if (source is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } diff --git a/src/libraries/System.Linq/src/System/Linq/Utilities.cs b/src/libraries/System.Linq/src/System/Linq/Utilities.cs index 2f0aeef28c6d11..208d6878040a48 100644 --- a/src/libraries/System.Linq/src/System/Linq/Utilities.cs +++ b/src/libraries/System.Linq/src/System/Linq/Utilities.cs @@ -6,7 +6,7 @@ namespace System.Linq { /// - /// Contains helper methods for System.Linq. Please put enumerable-related methods in . + /// Contains helper methods for System.Linq. /// internal static class Utilities { diff --git a/src/libraries/System.Linq/src/System/Linq/Where.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Where.SpeedOpt.cs index 858b3b62ab9ba3..ccea8233be9c9b 100644 --- a/src/libraries/System.Linq/src/System/Linq/Where.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Where.SpeedOpt.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Runtime.InteropServices; namespace System.Linq { @@ -34,26 +35,32 @@ public int GetCount(bool onlyIfCheap) public TSource[] ToArray() { - LargeArrayBuilder builder = new(); + SegmentedArrayBuilder.ScratchBuffer scratch = default; + SegmentedArrayBuilder builder = new(scratch); + Func predicate = _predicate; foreach (TSource item in _source) { - if (_predicate(item)) + if (predicate(item)) { builder.Add(item); } } - return builder.ToArray(); + TSource[] result = builder.ToArray(); + builder.Dispose(); + + return result; } public List ToList() { var list = new List(); + Func predicate = _predicate; foreach (TSource item in _source) { - if (_predicate(item)) + if (predicate(item)) { list.Add(item); } @@ -65,8 +72,13 @@ public List ToList() internal sealed partial class WhereArrayIterator : IIListProvider { - public int GetCount(bool onlyIfCheap) + public int GetCount(bool onlyIfCheap) => GetCount(onlyIfCheap, _source, _predicate); + + public static int GetCount(bool onlyIfCheap, ReadOnlySpan source, Func predicate) { + // In case someone uses Count() to force evaluation of + // the selector, run it provided `onlyIfCheap` is false. + if (onlyIfCheap) { return -1; @@ -74,42 +86,46 @@ public int GetCount(bool onlyIfCheap) int count = 0; - foreach (TSource item in _source) + foreach (TSource item in source) { - if (_predicate(item)) + if (predicate(item)) { - checked - { - count++; - } + checked { count++; } } } return count; } - public TSource[] ToArray() + public TSource[] ToArray() => ToArray(_source, _predicate); + + public static TSource[] ToArray(ReadOnlySpan source, Func predicate) { - var builder = new LargeArrayBuilder(_source.Length); + SegmentedArrayBuilder.ScratchBuffer scratch = default; + SegmentedArrayBuilder builder = new(scratch); - foreach (TSource item in _source) + foreach (TSource item in source) { - if (_predicate(item)) + if (predicate(item)) { builder.Add(item); } } - return builder.ToArray(); + TSource[] result = builder.ToArray(); + builder.Dispose(); + + return result; } public List ToList() { var list = new List(); + Func predicate = _predicate; foreach (TSource item in _source) { - if (_predicate(item)) + if (predicate(item)) { list.Add(item); } @@ -121,54 +137,18 @@ public List ToList() private sealed partial class WhereListIterator : Iterator, IIListProvider { - public int GetCount(bool onlyIfCheap) - { - if (onlyIfCheap) - { - return -1; - } + public int GetCount(bool onlyIfCheap) => WhereArrayIterator.GetCount(onlyIfCheap, CollectionsMarshal.AsSpan(_source), _predicate); - int count = 0; - - for (int i = 0; i < _source.Count; i++) - { - TSource item = _source[i]; - if (_predicate(item)) - { - checked - { - count++; - } - } - } - - return count; - } - - public TSource[] ToArray() - { - var builder = new LargeArrayBuilder(_source.Count); - - for (int i = 0; i < _source.Count; i++) - { - TSource item = _source[i]; - if (_predicate(item)) - { - builder.Add(item); - } - } - - return builder.ToArray(); - } + public TSource[] ToArray() => WhereArrayIterator.ToArray(CollectionsMarshal.AsSpan(_source), _predicate); public List ToList() { var list = new List(); - for (int i = 0; i < _source.Count; i++) + Func predicate = _predicate; + foreach (TSource item in CollectionsMarshal.AsSpan(_source)) { - TSource item = _source[i]; - if (_predicate(item)) + if (predicate(item)) { list.Add(item); } @@ -180,7 +160,9 @@ public List ToList() private sealed partial class WhereSelectArrayIterator : IIListProvider { - public int GetCount(bool onlyIfCheap) + public int GetCount(bool onlyIfCheap) => GetCount(onlyIfCheap, _source, _predicate, _selector); + + public static int GetCount(bool onlyIfCheap, ReadOnlySpan source, Func predicate, Func selector) { // In case someone uses Count() to force evaluation of // the selector, run it provided `onlyIfCheap` is false. @@ -192,11 +174,11 @@ public int GetCount(bool onlyIfCheap) int count = 0; - foreach (TSource item in _source) + foreach (TSource item in source) { - if (_predicate(item)) + if (predicate(item)) { - _selector(item); + selector(item); checked { count++; @@ -207,28 +189,35 @@ public int GetCount(bool onlyIfCheap) return count; } - public TResult[] ToArray() + public TResult[] ToArray() => ToArray(_source, _predicate, _selector); + + public static TResult[] ToArray(ReadOnlySpan source, Func predicate, Func selector) { - var builder = new LargeArrayBuilder(_source.Length); + SegmentedArrayBuilder.ScratchBuffer scratch = default; + SegmentedArrayBuilder builder = new(scratch); - foreach (TSource item in _source) + foreach (TSource item in source) { - if (_predicate(item)) + if (predicate(item)) { - builder.Add(_selector(item)); + builder.Add(selector(item)); } } - return builder.ToArray(); + TResult[] result = builder.ToArray(); + builder.Dispose(); + + return result; } public List ToList() { var list = new List(); + Func predicate = _predicate; foreach (TSource item in _source) { - if (_predicate(item)) + if (predicate(item)) { list.Add(_selector(item)); } @@ -240,58 +229,18 @@ public List ToList() private sealed partial class WhereSelectListIterator : IIListProvider { - public int GetCount(bool onlyIfCheap) - { - // In case someone uses Count() to force evaluation of - // the selector, run it provided `onlyIfCheap` is false. - - if (onlyIfCheap) - { - return -1; - } - - int count = 0; - - for (int i = 0; i < _source.Count; i++) - { - TSource item = _source[i]; - if (_predicate(item)) - { - _selector(item); - checked - { - count++; - } - } - } - - return count; - } - - public TResult[] ToArray() - { - var builder = new LargeArrayBuilder(_source.Count); + public int GetCount(bool onlyIfCheap) => WhereSelectArrayIterator.GetCount(onlyIfCheap, CollectionsMarshal.AsSpan(_source), _predicate, _selector); - for (int i = 0; i < _source.Count; i++) - { - TSource item = _source[i]; - if (_predicate(item)) - { - builder.Add(_selector(item)); - } - } - - return builder.ToArray(); - } + public TResult[] ToArray() => WhereSelectArrayIterator.ToArray(CollectionsMarshal.AsSpan(_source), _predicate, _selector); public List ToList() { var list = new List(); - for (int i = 0; i < _source.Count; i++) + Func predicate = _predicate; + foreach (TSource item in CollectionsMarshal.AsSpan(_source)) { - TSource item = _source[i]; - if (_predicate(item)) + if (predicate(item)) { list.Add(_selector(item)); } @@ -332,28 +281,36 @@ public int GetCount(bool onlyIfCheap) public TResult[] ToArray() { - LargeArrayBuilder builder = new(); + SegmentedArrayBuilder.ScratchBuffer scratch = default; + SegmentedArrayBuilder builder = new(scratch); + Func predicate = _predicate; + Func selector = _selector; foreach (TSource item in _source) { - if (_predicate(item)) + if (predicate(item)) { - builder.Add(_selector(item)); + builder.Add(selector(item)); } } - return builder.ToArray(); + TResult[] result = builder.ToArray(); + builder.Dispose(); + + return result; } public List ToList() { var list = new List(); + Func predicate = _predicate; + Func selector = _selector; foreach (TSource item in _source) { - if (_predicate(item)) + if (predicate(item)) { - list.Add(_selector(item)); + list.Add(selector(item)); } } diff --git a/src/libraries/System.Linq/tests/LifecycleTests.cs b/src/libraries/System.Linq/tests/LifecycleTests.cs index 1331c8223a847f..bafc26e7edd03b 100644 --- a/src/libraries/System.Linq/tests/LifecycleTests.cs +++ b/src/libraries/System.Linq/tests/LifecycleTests.cs @@ -21,7 +21,7 @@ from unary2 in UnaryOperations() from sink in Sinks() select (source, unary1, unary2, sink); - Assert.All(inputs, input => + foreach (var input in inputs) { var (source, unary1, unary2, sink) = input; var e = new LifecycleTrackingEnumerable(source.Work); @@ -43,7 +43,7 @@ from sink in Sinks() bool shortCircuits = argError || ShortCircuits(source, unary1, unary2, sink); Assert.InRange(e.EnumeratorCtorCalls, shortCircuits ? 0 : 1, 1); Assert.Equal(e.EnumeratorCtorCalls, e.EnumeratorDisposeCalls); - }); + } } [Fact] @@ -57,7 +57,7 @@ from binary in BinaryOperations() from sink in Sinks() select (source, unary, binary, sink); - Assert.All(inputs, input => + foreach (var input in inputs) { var (source, unary, binary, sink) = input; var es = new[] { new LifecycleTrackingEnumerable(source.Work), new LifecycleTrackingEnumerable(source.Work) }; @@ -82,7 +82,7 @@ from sink in Sinks() Assert.InRange(e.EnumeratorCtorCalls, shortCircuits ? 0 : 1, 1); Assert.Equal(e.EnumeratorCtorCalls, e.EnumeratorDisposeCalls); }); - }); + } } private static bool ShortCircuits(params Operation[] ops) => ops.Any(o => o.ShortCircuits); diff --git a/src/libraries/System.Linq/tests/ToArrayTests.cs b/src/libraries/System.Linq/tests/ToArrayTests.cs index b4877785112a74..bc1c47ffcf91a9 100644 --- a/src/libraries/System.Linq/tests/ToArrayTests.cs +++ b/src/libraries/System.Linq/tests/ToArrayTests.cs @@ -132,7 +132,10 @@ public void ToArray_FailOnExtremelyLargeCollection() { var largeSeq = new FastInfiniteEnumerator(); var thrownException = Assert.ThrowsAny(() => { largeSeq.ToArray(); }); - Assert.True(thrownException.GetType() == typeof(OverflowException) || thrownException.GetType() == typeof(OutOfMemoryException)); + Assert.True( + thrownException.GetType() == typeof(OverflowException) || + thrownException.GetType() == typeof(OutOfMemoryException), + $"Expected OverflowException or OutOfMemoryException, got {thrownException}"); } [Theory] From 499e2885ba3ff608950c38fa25ba1c1fc5169092 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Mon, 8 Jan 2024 20:17:35 +0100 Subject: [PATCH 14/20] [browser][mt] active issue (#96635) --- .../System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs index b2c035e6a8ad43..00e3ee669b662e 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.cs @@ -166,6 +166,7 @@ await Task.Delay(10).ContinueWith(_ => [Theory, MemberData(nameof(GetTargetThreads))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/96628")] public async Task ManagedDelay_ConfigureAwait_True(Executor executor) { await executor.Execute(async () => From 806c365cea411994210ea9ae59b4e5c5fa0c0b39 Mon Sep 17 00:00:00 2001 From: c0nd3v <32241825+c0nd3v@users.noreply.github.com> Date: Tue, 9 Jan 2024 02:57:32 +0700 Subject: [PATCH 15/20] Fix typo (#95569) --- .../Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs index bab1cdf3376e20..2bd1307cdb999a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs @@ -63,7 +63,7 @@ public ValueTask Task return default; } - // With normal access paterns, m_task should always be non-null here: the async method should have + // With normal access patterns, m_task should always be non-null here: the async method should have // either completed synchronously, in which case SetResult would have set m_task to a non-null object, // or it should be completing asynchronously, in which case AwaitUnsafeOnCompleted would have similarly // initialized m_task to a state machine object. However, if the type is used manually (not via From 17335f452dd158e84fe949632ea5a298f507b05a Mon Sep 17 00:00:00 2001 From: Tomas Weinfurt Date: Mon, 8 Jan 2024 14:15:44 -0800 Subject: [PATCH 16/20] add SerialStream support for MacCatalyst (#96492) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add SerialStream support for MacCatalyst * feedback from review * Explicitly set SupportedOSPlatforms for maccatalyst It'd default to the unsupported annotation from iOS otherwise. --------- Co-authored-by: Alexander Köplinger --- src/libraries/System.IO.Ports/Directory.Build.props | 1 + ...time.maccatalyst-arm64.runtime.native.System.IO.Ports.proj | 3 +++ ...untime.maccatalyst-x64.runtime.native.System.IO.Ports.proj | 3 +++ .../pkg/runtime.osx-arm64.runtime.native.System.IO.Ports.proj | 4 ---- .../System.IO.Ports/src/System/IO/Ports/SerialPort.Unix.cs | 2 ++ src/native/libs/CMakeLists.txt | 2 +- 6 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 src/libraries/System.IO.Ports/pkg/runtime.maccatalyst-arm64.runtime.native.System.IO.Ports.proj create mode 100644 src/libraries/System.IO.Ports/pkg/runtime.maccatalyst-x64.runtime.native.System.IO.Ports.proj diff --git a/src/libraries/System.IO.Ports/Directory.Build.props b/src/libraries/System.IO.Ports/Directory.Build.props index 776b99a2585c06..aa553090d0c926 100644 --- a/src/libraries/System.IO.Ports/Directory.Build.props +++ b/src/libraries/System.IO.Ports/Directory.Build.props @@ -3,5 +3,6 @@ true browser;ios;tvos + maccatalyst diff --git a/src/libraries/System.IO.Ports/pkg/runtime.maccatalyst-arm64.runtime.native.System.IO.Ports.proj b/src/libraries/System.IO.Ports/pkg/runtime.maccatalyst-arm64.runtime.native.System.IO.Ports.proj new file mode 100644 index 00000000000000..e71b9cb1bf5ffd --- /dev/null +++ b/src/libraries/System.IO.Ports/pkg/runtime.maccatalyst-arm64.runtime.native.System.IO.Ports.proj @@ -0,0 +1,3 @@ + + + diff --git a/src/libraries/System.IO.Ports/pkg/runtime.maccatalyst-x64.runtime.native.System.IO.Ports.proj b/src/libraries/System.IO.Ports/pkg/runtime.maccatalyst-x64.runtime.native.System.IO.Ports.proj new file mode 100644 index 00000000000000..e71b9cb1bf5ffd --- /dev/null +++ b/src/libraries/System.IO.Ports/pkg/runtime.maccatalyst-x64.runtime.native.System.IO.Ports.proj @@ -0,0 +1,3 @@ + + + diff --git a/src/libraries/System.IO.Ports/pkg/runtime.osx-arm64.runtime.native.System.IO.Ports.proj b/src/libraries/System.IO.Ports/pkg/runtime.osx-arm64.runtime.native.System.IO.Ports.proj index 800eff44ee6744..e71b9cb1bf5ffd 100644 --- a/src/libraries/System.IO.Ports/pkg/runtime.osx-arm64.runtime.native.System.IO.Ports.proj +++ b/src/libraries/System.IO.Ports/pkg/runtime.osx-arm64.runtime.native.System.IO.Ports.proj @@ -1,7 +1,3 @@ - - - true - diff --git a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialPort.Unix.cs b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialPort.Unix.cs index 46b4772f1d89d0..edcf919c2ea684 100644 --- a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialPort.Unix.cs +++ b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialPort.Unix.cs @@ -17,11 +17,13 @@ public static string[] GetPortNames() return OperatingSystem.IsLinux() ? GetPortNames_Linux() : OperatingSystem.IsAndroid() ? GetPortNames_Linux() : OperatingSystem.IsMacOS() ? GetPortNames_OSX() + : OperatingSystem.IsMacCatalyst() ? GetPortNames_OSX() : OperatingSystem.IsFreeBSD() ? GetPortNames_FreeBSD() #else return RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? GetPortNames_Linux() : RuntimeInformation.IsOSPlatform(OSPlatform.Create("ANDROID")) ? GetPortNames_Linux() : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? GetPortNames_OSX() + : RuntimeInformation.IsOSPlatform(OSPlatform.Create("MACCATALYST") ? GetPortNames_OSX() : RuntimeInformation.IsOSPlatform(OSPlatform.Create("FREEBSD")) ? GetPortNames_FreeBSD() #endif : throw new PlatformNotSupportedException(SR.PlatformNotSupported_SerialPort_GetPortNames); diff --git a/src/native/libs/CMakeLists.txt b/src/native/libs/CMakeLists.txt index 84416c199a9bdf..79b92e33da29e7 100644 --- a/src/native/libs/CMakeLists.txt +++ b/src/native/libs/CMakeLists.txt @@ -109,7 +109,7 @@ add_subdirectory(System.IO.Compression.Native) if (CLR_CMAKE_TARGET_UNIX OR CLR_CMAKE_TARGET_BROWSER OR CLR_CMAKE_TARGET_WASI) include(configure.cmake) - if (NOT CLR_CMAKE_TARGET_BROWSER AND NOT CLR_CMAKE_TARGET_WASI AND NOT CLR_CMAKE_TARGET_MACCATALYST AND NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS) + if (NOT CLR_CMAKE_TARGET_BROWSER AND NOT CLR_CMAKE_TARGET_WASI AND NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS) add_subdirectory(System.IO.Ports.Native) endif () From 0451127421fe7862f0e8407eafd6052d7ea3b175 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 8 Jan 2024 17:42:47 -0500 Subject: [PATCH 17/20] Remove extraneous use of unchecked from System.Linq (#96622) The code doesn't use a checked configuration, and if it did there are other expected overflows that would erroneously fail. --- .../src/System/Linq/Partition.SpeedOpt.cs | 26 +++++++++---------- .../src/System/Linq/Range.SpeedOpt.cs | 4 +-- .../System.Linq/src/System/Linq/Range.cs | 4 +-- .../src/System/Linq/Select.SpeedOpt.cs | 8 +++--- .../System.Linq/src/System/Linq/Where.cs | 4 +-- .../System.Linq/tests/SelectTests.cs | 2 +- src/libraries/System.Linq/tests/WhereTests.cs | 2 +- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs index 92f71cb058d478..202fb803881edb 100644 --- a/src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs @@ -26,14 +26,14 @@ public OrderedPartition(OrderedEnumerable source, int minIdxInclusive, public IPartition? Skip(int count) { - int minIndex = unchecked(_minIndexInclusive + count); - return unchecked((uint)minIndex > (uint)_maxIndexInclusive) ? null : new OrderedPartition(_source, minIndex, _maxIndexInclusive); + int minIndex = _minIndexInclusive + count; + return (uint)minIndex > (uint)_maxIndexInclusive ? null : new OrderedPartition(_source, minIndex, _maxIndexInclusive); } public IPartition Take(int count) { - int maxIndex = unchecked(_minIndexInclusive + count - 1); - if (unchecked((uint)maxIndex >= (uint)_maxIndexInclusive)) + int maxIndex = _minIndexInclusive + count - 1; + if ((uint)maxIndex >= (uint)_maxIndexInclusive) { return this; } @@ -43,7 +43,7 @@ public IPartition Take(int count) public TElement? TryGetElementAt(int index, out bool found) { - if (unchecked((uint)index <= (uint)(_maxIndexInclusive - _minIndexInclusive))) + if ((uint)index <= (uint)(_maxIndexInclusive - _minIndexInclusive)) { return _source.TryGetElementAt(index + _minIndexInclusive, out found); } @@ -96,7 +96,7 @@ public override bool MoveNext() // Having a separate field for the index would be more readable. However, we save it // into _state with a bias to minimize field size of the iterator. int index = _state - 1; - if (unchecked((uint)index <= (uint)(_maxIndexInclusive - _minIndexInclusive) && index < _source.Count - _minIndexInclusive)) + if ((uint)index <= (uint)(_maxIndexInclusive - _minIndexInclusive) && index < _source.Count - _minIndexInclusive) { _current = _source[_minIndexInclusive + index]; ++_state; @@ -118,13 +118,13 @@ public override IEnumerable Select(Func sele public IPartition Take(int count) { - int maxIndex = unchecked(_minIndexInclusive + count - 1); - return unchecked((uint)maxIndex >= (uint)_maxIndexInclusive) ? this : new ListPartition(_source, _minIndexInclusive, maxIndex); + int maxIndex = _minIndexInclusive + count - 1; + return (uint)maxIndex >= (uint)_maxIndexInclusive ? this : new ListPartition(_source, _minIndexInclusive, maxIndex); } public TSource? TryGetElementAt(int index, out bool found) { - if (unchecked((uint)index <= (uint)(_maxIndexInclusive - _minIndexInclusive) && index < _source.Count - _minIndexInclusive)) + if ((uint)index <= (uint)(_maxIndexInclusive - _minIndexInclusive) && index < _source.Count - _minIndexInclusive) { found = true; return _source[_minIndexInclusive + index]; @@ -286,7 +286,7 @@ internal EnumerablePartition(IEnumerable source, int minIndexInclusive, // on how many elements we can have. private bool HasLimit => _maxIndexInclusive != -1; - private int Limit => unchecked((_maxIndexInclusive + 1) - _minIndexInclusive); // This is that upper bound. + private int Limit => _maxIndexInclusive + 1 - _minIndexInclusive; // This is that upper bound. public override Iterator Clone() => new EnumerablePartition(_source, _minIndexInclusive, _maxIndexInclusive); @@ -388,7 +388,7 @@ public override IEnumerable Select(Func sele public IPartition? Skip(int count) { - int minIndex = unchecked(_minIndexInclusive + count); + int minIndex = _minIndexInclusive + count; if (!HasLimit) { @@ -414,7 +414,7 @@ public override IEnumerable Select(Func sele public IPartition Take(int count) { - int maxIndex = unchecked(_minIndexInclusive + count - 1); + int maxIndex = _minIndexInclusive + count - 1; if (!HasLimit) { if (maxIndex < 0) @@ -428,7 +428,7 @@ public IPartition Take(int count) return new EnumerablePartition(this, 0, count - 1); } } - else if (unchecked((uint)maxIndex >= (uint)_maxIndexInclusive)) + else if ((uint)maxIndex >= (uint)_maxIndexInclusive) { // If we don't know our max count, we can't go down this branch. // It's always possible for us to contain more than count items, as the rest diff --git a/src/libraries/System.Linq/src/System/Linq/Range.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Range.SpeedOpt.cs index 5337f0111e9d93..fc7c246cfb80cf 100644 --- a/src/libraries/System.Linq/src/System/Linq/Range.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Range.SpeedOpt.cs @@ -68,7 +68,7 @@ private static void Fill(Span destination, int value) } } - public int GetCount(bool onlyIfCheap) => unchecked(_end - _start); + public int GetCount(bool onlyIfCheap) => _end - _start; public int Count => _end - _start; @@ -95,7 +95,7 @@ public IPartition Take(int count) public int TryGetElementAt(int index, out bool found) { - if (unchecked((uint)index < (uint)(_end - _start))) + if ((uint)index < (uint)(_end - _start)) { found = true; return _start + index; diff --git a/src/libraries/System.Linq/src/System/Linq/Range.cs b/src/libraries/System.Linq/src/System/Linq/Range.cs index a01c2ff0f3625f..206f90415d52a1 100644 --- a/src/libraries/System.Linq/src/System/Linq/Range.cs +++ b/src/libraries/System.Linq/src/System/Linq/Range.cs @@ -37,7 +37,7 @@ public RangeIterator(int start, int count) { Debug.Assert(count > 0); _start = start; - _end = unchecked(start + count); + _end = start + count; } private int CountForDebugger => _end - _start; @@ -54,7 +54,7 @@ public override bool MoveNext() _state = 2; return true; case 2: - if (unchecked(++_current) == _end) + if (++_current == _end) { break; } diff --git a/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs index f59b6e7b08fbae..06f87db9c0f786 100644 --- a/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs @@ -144,7 +144,7 @@ public IPartition Take(int count) public TResult? TryGetElementAt(int index, out bool found) { - if (unchecked((uint)index < (uint)_source.Length)) + if ((uint)index < (uint)_source.Length) { found = true; return _selector(_source[index]); @@ -364,7 +364,7 @@ public IPartition Take(int count) public TResult? TryGetElementAt(int index, out bool found) { - if (unchecked((uint)index < (uint)_source.Count)) + if ((uint)index < (uint)_source.Count) { found = true; return _selector(_source[index]); @@ -467,7 +467,7 @@ public IPartition Take(int count) public TResult? TryGetElementAt(int index, out bool found) { - if (unchecked((uint)index < (uint)_source.Count)) + if ((uint)index < (uint)_source.Count) { found = true; return _selector(_source[index]); @@ -733,7 +733,7 @@ public override bool MoveNext() // Having a separate field for the index would be more readable. However, we save it // into _state with a bias to minimize field size of the iterator. int index = _state - 1; - if (unchecked((uint)index <= (uint)(_maxIndexInclusive - _minIndexInclusive) && index < _source.Count - _minIndexInclusive)) + if ((uint)index <= (uint)(_maxIndexInclusive - _minIndexInclusive) && index < _source.Count - _minIndexInclusive) { _current = _selector(_source[_minIndexInclusive + index]); ++_state; diff --git a/src/libraries/System.Linq/src/System/Linq/Where.cs b/src/libraries/System.Linq/src/System/Linq/Where.cs index a64233403d813e..aec6370a330f8e 100644 --- a/src/libraries/System.Linq/src/System/Linq/Where.cs +++ b/src/libraries/System.Linq/src/System/Linq/Where.cs @@ -171,7 +171,7 @@ public override bool MoveNext() int index = _state - 1; TSource[] source = _source; - while (unchecked((uint)index < (uint)source.Length)) + while ((uint)index < (uint)source.Length) { TSource item = source[index]; index = _state++; @@ -276,7 +276,7 @@ public override bool MoveNext() int index = _state - 1; TSource[] source = _source; - while (unchecked((uint)index < (uint)source.Length)) + while ((uint)index < (uint)source.Length) { TSource item = source[index]; index = _state++; diff --git a/src/libraries/System.Linq/tests/SelectTests.cs b/src/libraries/System.Linq/tests/SelectTests.cs index 1f709bf9caadaf..6cf77214a5b33b 100644 --- a/src/libraries/System.Linq/tests/SelectTests.cs +++ b/src/libraries/System.Linq/tests/SelectTests.cs @@ -1211,7 +1211,7 @@ public static IEnumerable RunSelectorDuringCountData() e => new LinkedList(e) // Implements IList. }; - var r = new Random(unchecked((int)0x984bf1a3)); + var r = new Random(42); for (int i = 0; i <= 5; i++) { diff --git a/src/libraries/System.Linq/tests/WhereTests.cs b/src/libraries/System.Linq/tests/WhereTests.cs index 302a01c21c4ee7..a6bc625e9e4936 100644 --- a/src/libraries/System.Linq/tests/WhereTests.cs +++ b/src/libraries/System.Linq/tests/WhereTests.cs @@ -1106,7 +1106,7 @@ public static IEnumerable ToCollectionData() private static IEnumerable GenerateRandomSequnce(uint seed, int count) { - var random = new Random(unchecked((int)seed)); + var random = new Random((int)seed); for (int i = 0; i < count; i++) { From f21dc6c3dceb6ea76bef73e2a026c770aaed3b5e Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Tue, 9 Jan 2024 00:48:17 +0100 Subject: [PATCH 18/20] PGO: Enable profiled casts by default (#96597) --- src/coreclr/jit/importer.cpp | 17 ++++++++++++----- src/coreclr/jit/jitconfigvalues.h | 2 +- .../superpmi/superpmi-shared/methodcontext.cpp | 8 +++++++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index acec2e45f68dc3..fed5a6ad0f2d64 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -5572,13 +5572,20 @@ GenTree* Compiler::impCastClassOrIsInstToTree( op2->gtFlags |= GTF_DONT_CSE; GenTreeCall* call = gtNewHelperCallNode(helper, TYP_REF, op2, op1); + + // Instrument this castclass/isinst if ((JitConfig.JitClassProfiling() > 0) && impIsCastHelperEligibleForClassProbe(call) && !isClassExact) { - HandleHistogramProfileCandidateInfo* pInfo = new (this, CMK_Inlining) HandleHistogramProfileCandidateInfo; - pInfo->ilOffset = ilOffset; - pInfo->probeIndex = info.compHandleHistogramProbeCount++; - call->gtHandleHistogramProfileCandidateInfo = pInfo; - compCurBB->SetFlags(BBF_HAS_HISTOGRAM_PROFILE); + // It doesn't make sense to instrument "x is T" or "(T)x" for shared T + if ((info.compCompHnd->getClassAttribs(pResolvedToken->hClass) & CORINFO_FLG_SHAREDINST) == 0) + { + HandleHistogramProfileCandidateInfo* pInfo = + new (this, CMK_Inlining) HandleHistogramProfileCandidateInfo; + pInfo->ilOffset = ilOffset; + pInfo->probeIndex = info.compHandleHistogramProbeCount++; + call->gtHandleHistogramProfileCandidateInfo = pInfo; + compCurBB->SetFlags(BBF_HAS_HISTOGRAM_PROFILE); + } } return call; } diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index cc57aba2cc5edb..753b7451b875c4 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -616,7 +616,7 @@ CONFIG_INTEGER(JitMinimalJitProfiling, W("JitMinimalJitProfiling"), 1) CONFIG_INTEGER(JitMinimalPrejitProfiling, W("JitMinimalPrejitProfiling"), 0) CONFIG_INTEGER(JitProfileValues, W("JitProfileValues"), 1) // Value profiling, e.g. Buffer.Memmove's size -CONFIG_INTEGER(JitProfileCasts, W("JitProfileCasts"), 0) // Profile castclass/isinst +CONFIG_INTEGER(JitProfileCasts, W("JitProfileCasts"), 1) // Profile castclass/isinst CONFIG_INTEGER(JitConsumeProfileForCasts, W("JitConsumeProfileForCasts"), 1) // Consume profile data (if any) for // castclass/isinst diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index f63042a2def778..a6d49f08219b5b 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -6925,8 +6925,14 @@ int MethodContext::repGetIntConfigValue(const WCHAR* name, int defaultValue) key.nameIndex = (DWORD)nameIndex; key.defaultValue = defaultValue; - DWORD value = LookupByKeyOrMissNoMessage(GetIntConfigValue, key); + int index = GetIntConfigValue->GetIndex(key); + if (index == -1) + { + // default value has changed + return defaultValue; + } + DWORD value = GetIntConfigValue->GetItem(index); DEBUG_REP(dmpGetIntConfigValue(key, value)); return (int)value; } From 315a2a8e918c6349cf0132e8a6c0f056188aa00b Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 8 Jan 2024 20:17:22 -0500 Subject: [PATCH 19/20] Add .NET 8 test project for System.Numerics.Tensors (#96641) * Add .NET 8 test project for System.Numerics.Tensors * Update src/libraries/System.Numerics.Tensors/tests/Net8Tests/System.Numerics.Tensors.Net8.Tests.csproj Co-authored-by: Viktor Hofer --------- Co-authored-by: Viktor Hofer --- .../System.Numerics.Tensors.sln | 7 +++++ .../System.Numerics.Tensors.Net8.Tests.csproj | 29 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 src/libraries/System.Numerics.Tensors/tests/Net8Tests/System.Numerics.Tensors.Net8.Tests.csproj diff --git a/src/libraries/System.Numerics.Tensors/System.Numerics.Tensors.sln b/src/libraries/System.Numerics.Tensors/System.Numerics.Tensors.sln index 12673da213f0fb..13d2981382e518 100644 --- a/src/libraries/System.Numerics.Tensors/System.Numerics.Tensors.sln +++ b/src/libraries/System.Numerics.Tensors/System.Numerics.Tensors.sln @@ -41,6 +41,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "tools\ref", "{9482D7 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{F9C2AAB1-C7B0-4E43-BB18-4FB16F6E272B}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Numerics.Tensors.Net8.Tests", "tests\Net8Tests\System.Numerics.Tensors.Net8.Tests.csproj", "{46AD9423-D8C3-44BB-A201-1CCCAB4C6DAF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -99,6 +101,10 @@ Global {57E57290-3A6A-43F8-8764-D4DC8151F89C}.Debug|Any CPU.Build.0 = Debug|Any CPU {57E57290-3A6A-43F8-8764-D4DC8151F89C}.Release|Any CPU.ActiveCfg = Release|Any CPU {57E57290-3A6A-43F8-8764-D4DC8151F89C}.Release|Any CPU.Build.0 = Release|Any CPU + {46AD9423-D8C3-44BB-A201-1CCCAB4C6DAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {46AD9423-D8C3-44BB-A201-1CCCAB4C6DAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {46AD9423-D8C3-44BB-A201-1CCCAB4C6DAF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {46AD9423-D8C3-44BB-A201-1CCCAB4C6DAF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -106,6 +112,7 @@ Global GlobalSection(NestedProjects) = preSolution {9F20CEA1-2216-4432-BBBD-F01E05D17F23} = {DE94CA7D-BB10-4865-85A6-6B694631247F} {4AF6A02D-82C8-4898-9EDF-01F107C25061} = {DE94CA7D-BB10-4865-85A6-6B694631247F} + {46AD9423-D8C3-44BB-A201-1CCCAB4C6DAF} = {DE94CA7D-BB10-4865-85A6-6B694631247F} {D311ABE4-10A9-4BB1-89CE-6358C55501A8} = {7AC4B2C7-A55C-4C4F-9B02-77F5CBFFF4AB} {21CB448A-3882-4337-B416-D1A3E0BCFFC5} = {7AC4B2C7-A55C-4C4F-9B02-77F5CBFFF4AB} {1578185F-C4FA-4866-936B-E62AAEDD03B7} = {DF0561A1-3AB8-4B51-AFB4-392EE1DD6247} diff --git a/src/libraries/System.Numerics.Tensors/tests/Net8Tests/System.Numerics.Tensors.Net8.Tests.csproj b/src/libraries/System.Numerics.Tensors/tests/Net8Tests/System.Numerics.Tensors.Net8.Tests.csproj new file mode 100644 index 00000000000000..eb08d0e5974a6f --- /dev/null +++ b/src/libraries/System.Numerics.Tensors/tests/Net8Tests/System.Numerics.Tensors.Net8.Tests.csproj @@ -0,0 +1,29 @@ + + + + + + $(NetCoreAppCurrent) + true + + + + + + + + + + + + + TargetFramework=net8.0 + + + + From b4ec422386bd358602c7693ff4ab28c96a806ec0 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 8 Jan 2024 21:49:36 -0500 Subject: [PATCH 20/20] Add debug-only use of new AssemblyBuilder.Save in Regex.CompileToAssembly (#96462) * Add debug-only use of new AssemblyBuilder.Save in Regex.CompileToAssembly To aid in debugging RegexCompiler issues and to help vet the new AssemblyBuilder.Save support. * Fix IL2121 warnings --------- Co-authored-by: Sven Boemer --- .../Reflection/Emit/ModuleBuilderImpl.cs | 1 - .../System/Reflection/Emit/TypeBuilderImpl.cs | 5 +- .../src/System.Text.RegularExpressions.csproj | 1 + .../System/Text/RegularExpressions/Regex.cs | 35 ++- .../RegexAssemblyCompiler.cs | 273 ++++++++++++++++++ .../Text/RegularExpressions/RegexCompiler.cs | 2 +- .../RegularExpressions/RegexLWCGCompiler.cs | 2 +- .../Regex.CompileToAssembly.Tests.cs | 36 ++- 8 files changed, 349 insertions(+), 6 deletions(-) create mode 100644 src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexAssemblyCompiler.cs diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs index f753a862e22874..2232bd17c61c34 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs @@ -185,7 +185,6 @@ internal void AppendMetadata(MethodBodyStreamEncoder methodBodyEncoder) } } - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072:DynamicallyAccessedMembers", Justification = "Members are retrieved from internal cache")] private void WriteInterfaceImplementations(TypeBuilderImpl typeBuilder, TypeDefinitionHandle typeHandle) { if (typeBuilder._interfaces != null) diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/TypeBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/TypeBuilderImpl.cs index 361ec7562aa3e1..1cf4d6009408e3 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/TypeBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/TypeBuilderImpl.cs @@ -176,13 +176,14 @@ private void ValidateAllAbstractMethodsAreImplemented() } [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2065:DynamicallyAccessedMembers", Justification = "Methods are loaded from this TypeBuilder. The interface methods should be available at this point")] - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:DynamicallyAccessedMembers", Justification = "The interface methods should be available at this point")] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2085:DynamicallyAccessedMembers", Justification = "Methods are loaded from this TypeBuilder")] private void CheckInterfaces(Type[] _interfaces) { foreach (Type interfaceType in _interfaces) { +#pragma warning disable IL2075 // Analyzer produces a different warning code than illink. The IL2065 suppression takes care of illink: https://github.com/dotnet/runtime/issues/96646 MethodInfo[] interfaceMethods = interfaceType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); +#pragma warning restore IL2075 for (int i = 0; i < interfaceMethods.Length; i++) { MethodInfo interfaceMethod = interfaceMethods[i]; @@ -195,7 +196,9 @@ private void CheckInterfaces(Type[] _interfaces) } // Check parent interfaces too +#pragma warning disable IL2075 // Analyzer produces a different warning code than illink. The IL2065 suppression takes care of illink: https://github.com/dotnet/runtime/issues/96646 CheckInterfaces(interfaceType.GetInterfaces()); +#pragma warning restore IL2075 } } diff --git a/src/libraries/System.Text.RegularExpressions/src/System.Text.RegularExpressions.csproj b/src/libraries/System.Text.RegularExpressions/src/System.Text.RegularExpressions.csproj index 524e7f79e687de..1b77579511ab8f 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System.Text.RegularExpressions.csproj +++ b/src/libraries/System.Text.RegularExpressions/src/System.Text.RegularExpressions.csproj @@ -28,6 +28,7 @@ + diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.cs index 3053eb20550994..c8179c064a9adb 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.cs @@ -227,8 +227,41 @@ public static void CompileToAssembly(RegexCompilationInfo[] regexinfos, Assembly CompileToAssembly(regexinfos, assemblyname, attributes, null); [Obsolete(Obsoletions.RegexCompileToAssemblyMessage, DiagnosticId = Obsoletions.RegexCompileToAssemblyDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] - public static void CompileToAssembly(RegexCompilationInfo[] regexinfos, AssemblyName assemblyname, CustomAttributeBuilder[]? attributes, string? resourceFile) => + public static void CompileToAssembly(RegexCompilationInfo[] regexinfos, AssemblyName assemblyname, CustomAttributeBuilder[]? attributes, string? resourceFile) + { +#if DEBUG + // This code exists only to help with the development of the RegexCompiler. + // .NET no longer supports CompileToAssembly; the source generator should be used instead. +#pragma warning disable IL3050 + ArgumentNullException.ThrowIfNull(assemblyname); + ArgumentNullException.ThrowIfNull(regexinfos); + + var c = new RegexAssemblyCompiler(assemblyname, attributes, resourceFile); + + for (int i = 0; i < regexinfos.Length; i++) + { + ArgumentNullException.ThrowIfNull(regexinfos[i]); + + string pattern = regexinfos[i].Pattern; + + RegexOptions options = regexinfos[i].Options | RegexOptions.Compiled; // ensure compiled is set; it enables more optimization specific to compilation + + string fullname = regexinfos[i].Namespace.Length == 0 ? + regexinfos[i].Name : + regexinfos[i].Namespace + "." + regexinfos[i].Name; + + RegexTree tree = RegexParser.Parse(pattern, options, (options & RegexOptions.CultureInvariant) != 0 ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture); + RegexInterpreterCode code = RegexWriter.Write(tree); + + c.GenerateRegexType(pattern, options, fullname, regexinfos[i].IsPublic, tree, code, regexinfos[i].MatchTimeout); + } + + c.Save(assemblyname.Name ?? "RegexCompileToAssembly"); +#pragma warning restore IL3050 +#else throw new PlatformNotSupportedException(SR.PlatformNotSupported_CompileToAssembly); +#endif + } /// /// Escapes a minimal set of metacharacters (\, *, +, ?, |, {, [, (, ), ^, $, ., #, and diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexAssemblyCompiler.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexAssemblyCompiler.cs new file mode 100644 index 00000000000000..ff75e5d6534182 --- /dev/null +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexAssemblyCompiler.cs @@ -0,0 +1,273 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Reflection.Emit; +using System.Threading; + +// WARNING: +// The code generated by RegexCompiler is not intended to be saved to disk and loaded back later. +// It accesses internal and private members of System.Text.RegularExpressions, which may change +// at any point in the future, and relies on details about the current machine/process, e.g. is +// it 32-bit or 64-bit. The generated surface area has also not been vetted. This code exists +// only for debugging purposes, to make it easier to examine the IL that RegexCompiler emits. + +#if DEBUG +namespace System.Text.RegularExpressions +{ + /// Compiles a Regex to an assembly that can be saved to disk. + [RequiresDynamicCode("The RegexAssemblyCompiler type requires dynamic code to be enabled.")] + internal sealed class RegexAssemblyCompiler : RegexCompiler + { + /// Type count used to augment generated type names to create unique names. + private static int s_typeCount; + + private readonly AssemblyBuilder _assembly; + private readonly ModuleBuilder _module; + private readonly MethodInfo _save; + + internal RegexAssemblyCompiler(AssemblyName an, CustomAttributeBuilder[]? attribs, string? resourceFile) + { + if (resourceFile != null) + { + // Unmanaged resources are not supported: _assembly.DefineUnmanagedResource(resourceFile); + throw new PlatformNotSupportedException(); + } + + // TODO: Use public API when it's available: https://github.com/dotnet/runtime/issues/15704 + Type abType = Type.GetType("System.Reflection.Emit.AssemblyBuilderImpl, System.Reflection.Emit", throwOnError: true)!; + MethodInfo defineDynamicAssembly = abType.GetMethod("DefinePersistedAssembly", + BindingFlags.NonPublic | BindingFlags.Static, + [typeof(AssemblyName), typeof(Assembly), typeof(List)]) ?? + throw new InvalidOperationException("Could not find method AssemblyBuilderImpl.DefinePersistedAssembly"); + _assembly = (AssemblyBuilder?)defineDynamicAssembly.Invoke(null, [an, typeof(object).Assembly, attribs is not null ? new List(attribs) : null]) ?? + throw new InvalidOperationException("DefinePersistedAssembly returned null"); + _save = abType.GetMethod("Save", BindingFlags.NonPublic | BindingFlags.Instance, [typeof(string)]) ?? + throw new InvalidOperationException("Could not find method AssemblyBuilderImpl.Save"); + + _module = _assembly.DefineDynamicModule(an.Name + ".dll"); + } + + internal void GenerateRegexType(string pattern, RegexOptions options, string name, bool isPublic, RegexTree tree, RegexInterpreterCode code, TimeSpan matchTimeout) + { + // Store arguments into the base type's fields + _options = options; + _regexTree = tree; + + // Pick a name for the class. + string typenumString = ((uint)Interlocked.Increment(ref s_typeCount)).ToString(); + + // Generate the RegexRunner-derived type. + TypeBuilder regexRunnerTypeBuilder = DefineType(_module, $"{name}Runner{typenumString}", isPublic: false, isSealed: true, typeof(RegexRunner)); + + _ilg = DefineMethod(regexRunnerTypeBuilder, "TryFindNextPossibleStartingPosition", [typeof(ReadOnlySpan)], typeof(bool), out MethodBuilder tryFindNextPossibleStartingPositionMethod); + EmitTryFindNextPossibleStartingPosition(); + + _ilg = DefineMethod(regexRunnerTypeBuilder, "TryMatchAtCurrentPosition", [typeof(ReadOnlySpan)], typeof(bool), out MethodBuilder tryMatchAtCurrentPositionMethod); + EmitTryMatchAtCurrentPosition(); + + _ilg = DefineMethod(regexRunnerTypeBuilder, "Scan", [typeof(ReadOnlySpan)], null, out _); + EmitScan(options, tryFindNextPossibleStartingPositionMethod, tryMatchAtCurrentPositionMethod); + + Type runnerType = regexRunnerTypeBuilder.CreateType()!; + + // Generate the RegexRunnerFactory-derived type. + TypeBuilder regexRunnerFactoryTypeBuilder = DefineType(_module, $"{name}Factory{typenumString}", isPublic: false, isSealed: true, typeof(RegexRunnerFactory)); + _ilg = DefineMethod(regexRunnerFactoryTypeBuilder, "CreateInstance", null, typeof(RegexRunner), out _); + GenerateCreateInstance(runnerType); + Type regexRunnerFactoryType = regexRunnerFactoryTypeBuilder.CreateType()!; + + // Generate the Regex-derived type. + TypeBuilder regexTypeBuilder = DefineType(_module, name, isPublic, isSealed: false, typeof(Regex)); + ConstructorBuilder defaultCtorBuilder = regexTypeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes); + _ilg = defaultCtorBuilder.GetILGenerator(); + GenerateRegexDefaultCtor(pattern, options, regexRunnerFactoryType, tree, code, matchTimeout); + if (matchTimeout != Regex.InfiniteMatchTimeout) + { + // We only generate a constructor with a timeout parameter if the regex information supplied has a non-infinite timeout. + // If it has an infinite timeout, then the generated code is not going to respect the timeout. This is a difference from netfx, + // due to the fact that we now special-case an infinite timeout in the code generator to avoid spitting unnecessary code + // and paying for the checks at run time. + _ilg = regexTypeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { typeof(TimeSpan) }).GetILGenerator(); + GenerateRegexTimeoutCtor(defaultCtorBuilder, regexTypeBuilder); + } + regexTypeBuilder.CreateType(); + } + + /// Generates a very simple factory method. + private void GenerateCreateInstance([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type) + { + // return new Type(); + _ilg!.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes)!); + Ret(); + } + + private void GenerateRegexDefaultCtor( + string pattern, + RegexOptions options, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type regexRunnerFactoryType, + RegexTree tree, + RegexInterpreterCode code, + TimeSpan matchTimeout) + { + // Call the base ctor and store pattern, options, and factory. + // base.ctor(); + // base.pattern = pattern; + // base.options = options; + // base.factory = new DerivedRegexRunnerFactory(); + Ldthis(); + _ilg!.Emit(OpCodes.Call, typeof(Regex).GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, Array.Empty())!); + Ldthis(); + Ldstr(pattern); + Stfld(RegexField(nameof(Regex.pattern))); + Ldthis(); + Ldc((int)options); + Stfld(RegexField(nameof(Regex.roptions))); + Ldthis(); + _ilg!.Emit(OpCodes.Newobj, regexRunnerFactoryType.GetConstructor(Type.EmptyTypes)!); + Stfld(RegexField(nameof(Regex.factory))); + + // Store the timeout (no need to validate as it should have happened in RegexCompilationInfo) + Ldthis(); + if (matchTimeout == Regex.InfiniteMatchTimeout) + { + // base.internalMatchTimeout = Regex.InfiniteMatchTimeout; + _ilg.Emit(OpCodes.Ldsfld, RegexField(nameof(Regex.InfiniteMatchTimeout))); + } + else + { + // base.internalMatchTimeout = TimeSpan.FromTick(matchTimeout.Ticks); + LdcI8(matchTimeout.Ticks); + Call(typeof(TimeSpan).GetMethod(nameof(TimeSpan.FromTicks), BindingFlags.Public | BindingFlags.Static)!); + } + Stfld(RegexField(nameof(Regex.internalMatchTimeout))); + + // Set capsize, caps, capnames, capslist. + Ldthis(); + Ldc(tree.CaptureCount); + Stfld(RegexField(nameof(Regex.capsize))); + if (tree.CaptureNumberSparseMapping != null) + { + // Caps = new Hashtable {{0, 0}, {1, 1}, ... }; + GenerateCreateHashtable(RegexField(nameof(Regex.caps)), tree.CaptureNumberSparseMapping); + } + if (tree.CaptureNameToNumberMapping != null) + { + // CapNames = new Hashtable {{"0", 0}, {"1", 1}, ...}; + GenerateCreateHashtable(RegexField(nameof(Regex.capnames)), tree.CaptureNameToNumberMapping); + } + if (tree.CaptureNames != null) + { + // capslist = new string[...]; + // capslist[0] = "0"; + // capslist[1] = "1"; + // ... + Ldthis(); + Ldc(tree.CaptureNames.Length); + _ilg.Emit(OpCodes.Newarr, typeof(string)); // create new string array + FieldInfo capslistField = RegexField(nameof(Regex.capslist)); + Stfld(capslistField); + for (int i = 0; i < tree.CaptureNames.Length; i++) + { + Ldthisfld(capslistField); + Ldc(i); + Ldstr(tree.CaptureNames[i]); + _ilg.Emit(OpCodes.Stelem_Ref); + } + } + + // return; + Ret(); + } + + private void GenerateRegexTimeoutCtor(ConstructorBuilder defaultCtorBuilder, TypeBuilder regexTypeBuilder) + { + // base.ctor(); + // ValidateMatchTimeout(timeSpan); + // base.internalMatchTimeout = timeSpan; + Ldthis(); + _ilg!.Emit(OpCodes.Call, defaultCtorBuilder); + _ilg.Emit(OpCodes.Ldarg_1); + Call(typeof(Regex).GetMethod(nameof(Regex.ValidateMatchTimeout), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)!); + Ldthis(); + _ilg.Emit(OpCodes.Ldarg_1); + Stfld(RegexField(nameof(Regex.internalMatchTimeout))); + Ret(); + } + + internal void GenerateCreateHashtable(FieldInfo field, Hashtable ht) + { + // hashtable = new Hashtable(); + Ldthis(); + _ilg!.Emit(OpCodes.Newobj, typeof(Hashtable).GetConstructor(Type.EmptyTypes)!); + Stfld(field); + + // hashtable.Add(key1, value1); + // hashtable.Add(key2, value2); + // ... + MethodInfo addMethod = typeof(Hashtable).GetMethod(nameof(Hashtable.Add), BindingFlags.Public | BindingFlags.Instance)!; + IDictionaryEnumerator en = ht.GetEnumerator(); + while (en.MoveNext()) + { + Ldthisfld(field); + + if (en.Key is int key) + { + Ldc(key); + _ilg!.Emit(OpCodes.Box, typeof(int)); + } + else + { + Ldstr((string)en.Key); + } + + Ldc((int)en.Value!); + _ilg!.Emit(OpCodes.Box, typeof(int)); + Callvirt(addMethod); + } + } + + /// Gets the named instance field from the Regex type. + private static FieldInfo RegexField(string fieldname) => + typeof(Regex).GetField(fieldname, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)!; + + /// Saves the assembly to a file in the current directory based on the assembly's name. + internal void Save(string fileName) + { + if (!fileName.EndsWith(".dll", StringComparison.Ordinal)) + { + fileName += ".dll"; + } + + _save.Invoke(_assembly, [fileName]); // TODO: Use public API when it's available: https://github.com/dotnet/runtime/issues/15704 + } + + /// Begins the definition of a new type with a specified base class + private static TypeBuilder DefineType( + ModuleBuilder moduleBuilder, + string typeName, + bool isPublic, + bool isSealed, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type inheritFromClass) + { + TypeAttributes attrs = TypeAttributes.Class | TypeAttributes.BeforeFieldInit | (isPublic ? TypeAttributes.Public : TypeAttributes.NotPublic); + if (isSealed) + { + attrs |= TypeAttributes.Sealed; + } + + return moduleBuilder.DefineType(typeName, attrs, inheritFromClass); + } + + /// Begins the definition of a new method (no args) with a specified return value. + private static ILGenerator DefineMethod(TypeBuilder typeBuilder, string methname, Type[]? parameterTypes, Type? returnType, out MethodBuilder builder) + { + builder = typeBuilder.DefineMethod(methname, MethodAttributes.Family | MethodAttributes.Virtual, returnType, parameterTypes); + return builder.GetILGenerator(); + } + } +} +#endif diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCompiler.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCompiler.cs index 6305e748b9cb88..a1547f0bb340d0 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCompiler.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCompiler.cs @@ -5305,7 +5305,7 @@ void EmitStackPop() } } - protected void EmitScan(RegexOptions options, DynamicMethod tryFindNextStartingPositionMethod, DynamicMethod tryMatchAtCurrentPositionMethod) + protected void EmitScan(RegexOptions options, MethodInfo tryFindNextStartingPositionMethod, MethodInfo tryMatchAtCurrentPositionMethod) { // As with the source generator, we can emit special code for common circumstances rather than always emitting // the most general purpose scan loop. Unlike the source generator, however, code appearance isn't important diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexLWCGCompiler.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexLWCGCompiler.cs index 3d896e1ca7264f..f8b9a15e0b193b 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexLWCGCompiler.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexLWCGCompiler.cs @@ -24,7 +24,7 @@ internal sealed class RegexLWCGCompiler : RegexCompiler /// private static readonly bool s_includePatternInName = Environment.GetEnvironmentVariable(IncludePatternInNamesEnvVar) == "1"; - /// Parameter types for the generated Go and FindFirstChar methods. + /// Parameter types for the generated TryFindNextPossibleStartingPosition and TryMatchAtCurrentPosition methods. private static readonly Type[] s_paramTypes = [typeof(RegexRunner), typeof(ReadOnlySpan)]; /// Id number to use for the next compiled regex. diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.CompileToAssembly.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.CompileToAssembly.Tests.cs index 168156c6a95ef6..9a95653af72e77 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.CompileToAssembly.Tests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.CompileToAssembly.Tests.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.IO; +using System.Linq; using System.Reflection; using System.Reflection.Emit; using Xunit; @@ -11,7 +13,10 @@ namespace System.Text.RegularExpressions.Tests [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] public class RegexCompileToAssemblyTests : FileCleanupTestBase { - [Fact] + public static bool IsDebug => typeof(Regex).Assembly.GetCustomAttributes(false).OfType().Any(da => da.IsJITTrackingEnabled); + public static bool IsRelease => !IsDebug; + + [ConditionalFact(nameof(IsRelease))] public void CompileToAssembly_PNSE() { Assert.Throws(() => Regex.CompileToAssembly(null, null)); @@ -33,5 +38,34 @@ [new RegexCompilationInfo("abcd", RegexOptions.None, "abcd", "SomeNamespace", tr [new CustomAttributeBuilder(typeof(AssemblyCompanyAttribute).GetConstructor([typeof(string)]), new[] { "TestCompany" })], "resourceFile")); } + + [ConditionalFact(nameof(IsDebug))] + public void CompileToAssembly_SimpleUseInDebug() + { + (RegexCompilationInfo rci, string validInput, string invalidInput)[] regexes = + [ + (new RegexCompilationInfo("abcd", RegexOptions.None, "Type1", "Namespace1", ispublic: true), "123abcd123", "123abed123"), + (new RegexCompilationInfo("(a|b|cde)+", RegexOptions.None, "Type2", "Namespace2.Sub", ispublic: true), "abcde", "cd"), + ]; + + string assemblyName = Path.GetRandomFileName(); + + string cwd = Environment.CurrentDirectory; + Environment.CurrentDirectory = TestDirectory; + try + { + Regex.CompileToAssembly(regexes.Select(r => r.rci).ToArray(), new AssemblyName(assemblyName)); + } + finally + { + Environment.CurrentDirectory = cwd; + } + + string assemblyPath = Path.Combine(TestDirectory, assemblyName + ".dll"); + Assert.True(File.Exists(assemblyPath)); + + // Uncomment to save the assembly to the desktop for inspection: + // File.Copy(assemblyPath, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), Path.GetFileName(assemblyPath))); + } } }