diff --git a/.dscanner.ini b/.dscanner.ini
index 65948fa71fb..12ceca66fac 100644
--- a/.dscanner.ini
+++ b/.dscanner.ini
@@ -145,7 +145,6 @@ assert_without_msg="-etc.c.SQL_,\
-std.datetime.interval,\
-std.datetime.systime,\
-std.datetime.timezone,\
--std.digest.digest,\
-std.digest.murmurhash,\
-std.digest.sha,\
-std.encoding,\
@@ -208,12 +207,11 @@ assert_without_msg="-etc.c.SQL_,\
-std.utf,\
-std.uuid,\
-std.variant,\
--std.windows.registry,\
--std.xml"
+-std.windows.registry"
; Checks for assignment to auto-ref function parameters
auto_ref_assignment_check="-std.algorithm.mutation,-std.typecons"
; Checks for variables that could be declared immutable
-could_be_immutable_check="-std.algorithm.comparison,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.setops,-std.algorithm.sorting,-std.array,-std.base64,-std.bigint,-std.bitmanip,-std.checkedint,-std.complex,-std.concurrency,-std.container,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.container.util,-std.conv,-std.csv,-std.datetime,-std.datetime.date,-std.datetime.interval,-std.datetime.stopwatch,-std.datetime.systime,-std.datetime.timezone,-std.digest.crc,-std.digest,-std.digest.hmac,-std.digest.md,-std.digest.murmurhash,-std.digest.ripemd,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bitmapped_block,-std.experimental.allocator.building_blocks.bucketizer,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.stats_collector,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.allocator.typed,-std.experimental.checkedint,-std.logger.core,-std.logger.multilogger,-std.experimental.typecons,-std.file,-std.format,-std.functional,-std.getopt,-std.internal.cstring,-std.internal.digest.sha_SSSE3,-std.internal.math.biguintcore,-std.internal.math.biguintnoasm,-std.internal.math.biguintx86,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.scopebuffer,-std.internal.test.dummyrange,-std.json,-std.math,-std.mathspecial,-std.meta,-std.mmfile,-std.net.curl,-std.net.isemail,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.interfaces,-std.range.primitives,-std.regex,-std.regex.internal.backtracking,-std.regex.internal.generator,-std.regex.internal.ir,-std.regex.internal.kickstart,-std.regex.internal.parser,-std.regex.internal.tests,-std.regex.internal.thompson,-std.signals,-std.socket,-std.stdio,-std.string,-std.sumtype,-std.traits,-std.typecons,-std.uni,-std.uri,-std.utf,-std.uuid,-std.variant,-std.windows.registry,-std.xml,-std.zip,-std.zlib"
+could_be_immutable_check="-std.algorithm.comparison,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.setops,-std.algorithm.sorting,-std.array,-std.base64,-std.bigint,-std.bitmanip,-std.checkedint,-std.complex,-std.concurrency,-std.container,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.container.util,-std.conv,-std.csv,-std.datetime,-std.datetime.date,-std.datetime.interval,-std.datetime.stopwatch,-std.datetime.systime,-std.datetime.timezone,-std.digest.crc,-std.digest,-std.digest.hmac,-std.digest.md,-std.digest.murmurhash,-std.digest.ripemd,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bitmapped_block,-std.experimental.allocator.building_blocks.bucketizer,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.stats_collector,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.allocator.typed,-std.experimental.checkedint,-std.logger.core,-std.logger.multilogger,-std.file,-std.format,-std.functional,-std.getopt,-std.internal.cstring,-std.internal.digest.sha_SSSE3,-std.internal.math.biguintcore,-std.internal.math.biguintnoasm,-std.internal.math.biguintx86,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.scopebuffer,-std.internal.test.dummyrange,-std.json,-std.math,-std.mathspecial,-std.meta,-std.mmfile,-std.net.curl,-std.net.isemail,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.interfaces,-std.range.primitives,-std.regex,-std.regex.internal.backtracking,-std.regex.internal.generator,-std.regex.internal.ir,-std.regex.internal.kickstart,-std.regex.internal.parser,-std.regex.internal.tests,-std.regex.internal.thompson,-std.signals,-std.socket,-std.stdio,-std.string,-std.sumtype,-std.traits,-std.typecons,-std.uni,-std.uri,-std.utf,-std.uuid,-std.variant,-std.windows.registry,-std.zip,-std.zlib"
; Check for poor exception handling practices
exception_check="-std.concurrency,-std.net.curl,-std.parallelism,-std.range,-std.socket,-std.typecons"
; Checks for poor placement of function attributes
@@ -247,7 +245,6 @@ has_public_example="-etc.c.curl,\
-std.logger.core,\
-std.logger.filelogger,\
-std.logger.multilogger,\
--std.experimental.typecons,\
-std.getopt,\
-std.internal.math.biguintcore,\
-std.internal.math.biguintnoasm,\
@@ -271,14 +268,13 @@ has_public_example="-etc.c.curl,\
-std.stdio,\
-std.sumtype,\
-std.uni,\
--std.xml,\
-std.zip,\
-std.zlib"
; Check for sortedness of imports
imports_sortedness="+disabled"
-;imports_sortedness="-etc.c.curl,-std.algorithm.comparison,-std.algorithm.internal,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.setops,-std.algorithm.sorting,-std.array,-std.bigint,-std.bitmanip,-std.c.freebsd.socket,-std.c.linux.pthread,-std.c.process,-std.complex,-std.concurrency,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.container.util,-std.conv,-std.datetime,-std.datetime.date,-std.datetime.interval,-std.datetime.systime,-std.datetime.timezone,-std.digest,-std.digest.hmac,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.common,-std.experimental.allocator.mallocator,-std.experimental.allocator.mmap_allocator,-std.experimental.allocator.showcase,-std.experimental.allocator.typed,-std.experimental.checkedint,-std.logger.core,-std.experimental.typecons,-std.file,-std.format,-std.functional,-std.getopt,-std.internal.math.biguintcore,-std.internal.test.dummyrange,-std.json,-std.math,-std.meta,-std.mmfile,-std.net.curl,-std.net.isemail,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.primitives,-std.regex,-std.regex.internal.backtracking,-std.regex.internal.generator,-std.regex.internal.kickstart,-std.regex.internal.parser,-std.regex.internal.tests,-std.signals,-std.socket,-std.stdio,-std.string,-std.uni,-std.utf,-std.uuid,-std.variant,-std.windows.charset,-std.windows.registry,-std.windows.syserror,-std.zip"
+;imports_sortedness="-etc.c.curl,-std.algorithm.comparison,-std.algorithm.internal,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.setops,-std.algorithm.sorting,-std.array,-std.bigint,-std.bitmanip,-std.c.freebsd.socket,-std.c.linux.pthread,-std.c.process,-std.complex,-std.concurrency,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.container.util,-std.conv,-std.datetime,-std.datetime.date,-std.datetime.interval,-std.datetime.systime,-std.datetime.timezone,-std.digest,-std.digest.hmac,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.common,-std.experimental.allocator.mallocator,-std.experimental.allocator.mmap_allocator,-std.experimental.allocator.showcase,-std.experimental.allocator.typed,-std.experimental.checkedint,-std.logger.core,-std.file,-std.format,-std.functional,-std.getopt,-std.internal.math.biguintcore,-std.internal.test.dummyrange,-std.json,-std.math,-std.meta,-std.mmfile,-std.net.curl,-std.net.isemail,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.primitives,-std.regex,-std.regex.internal.backtracking,-std.regex.internal.generator,-std.regex.internal.kickstart,-std.regex.internal.parser,-std.regex.internal.tests,-std.signals,-std.socket,-std.stdio,-std.string,-std.uni,-std.utf,-std.uuid,-std.variant,-std.windows.charset,-std.windows.registry,-std.windows.syserror,-std.zip"
; Checks for labels with the same name as variables
-label_var_same_name_check="-std.algorithm.iteration,-std.algorithm.sorting,-std.array,-std.bigint,-std.bitmanip,-std.conv,-std.encoding,-std.experimental.allocator.building_blocks.segregator,-std.experimental.typecons,-std.internal.digest.sha_SSSE3,-std.parallelism,-std.process,-std.typecons,-std.utf,-std.traits"
+label_var_same_name_check="-std.algorithm.iteration,-std.algorithm.sorting,-std.array,-std.bigint,-std.bitmanip,-std.conv,-std.encoding,-std.experimental.allocator.building_blocks.segregator,-std.internal.digest.sha_SSSE3,-std.parallelism,-std.process,-std.typecons,-std.utf,-std.traits"
; Checks for subtraction from .length properties
length_subtraction_check="+disabled"
;length_subtraction_check="-std.algorithm.internal,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.sorting,-std.array,-std.concurrency,-std.container.array,-std.container.binaryheap,-std.conv,-std.datetime.timezone,-std.experimental.allocator.building_blocks.segregator,-std.logger.core,-std.file,-std.format,-std.getopt,-std.internal.math.biguintcore,-std.internal.math.biguintnoasm,-std.internal.math.biguintx86,-std.internal.scopebuffer,-std.math,-std.net.curl,-std.net.isemail,-std.numeric,-std.parallelism,-std.path,-std.process,-std.range,-std.regex,-std.regex.internal.parser,-std.regex.internal.tests,-std.string,-std.uni,-std.windows.charset,-std.windows.registry,-std.zip"
@@ -294,7 +290,7 @@ number_style_check="+disabled"
;number_style_check="-std.algorithm.iteration,-std.algorithm.sorting,-std.array,-std.bigint,-std.bitmanip,-std.container.array,-std.conv,-std.datetime.date,-std.datetime.systime,-std.datetime.timezone,-std.digest.crc,-std.digest,-std.digest.md,-std.digest.ripemd,-std.digest.sha,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.checkedint,-std.file,-std.format,-std.functional,-std.internal.math.biguintcore,-std.internal.math.gammafunction,-std.json,-std.math,-std.outbuffer,-std.parallelism,-std.random,-std.range,-std.regex.internal.generator,-std.utf,-std.zip,-std.zlib"
; Checks that opEquals, opCmp, toHash, and toString are either const, immutable
; , or inout.
-object_const_check="-std.algorithm.searching,-std.array,-std.bitmanip,-std.checkedint,-std.concurrency,-std.container.rbtree,-std.conv,-std.datetime.interval,-std.encoding,-std.exception,-std.experimental.checkedint,-std.logger.core,-std.experimental.typecons,-std.format,-std.functional,-std.meta,-std.numeric,-std.range,-std.regex,-std.stdio,-std.sumtype,-std.typecons,-std.variant,-std.xml"
+object_const_check="-std.algorithm.searching,-std.array,-std.bitmanip,-std.checkedint,-std.concurrency,-std.container.rbtree,-std.conv,-std.datetime.interval,-std.encoding,-std.exception,-std.experimental.checkedint,-std.logger.core,-std.format,-std.functional,-std.meta,-std.numeric,-std.range,-std.regex,-std.stdio,-std.sumtype,-std.typecons,-std.variant"
; Checks that opEquals and toHash are both defined or neither are defined
opequals_tohash_check="-std.complex,-std.container.array,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.datetime,-std.datetime.date,-std.experimental.checkedint,-std.internal.test.dummyrange,-std.json,-std.numeric,-std.random,-std.socket,-std.sumtype,-std.typecons,-std.uni"
; Check for properly documented public functions (Returns, Params)
@@ -395,16 +391,15 @@ properly_documented_public_functions="-etc.c.odbc.sql,\
-std.utf,\
-std.uuid,\
-std.variant,\
--std.xml,\
-std.zlib"
; Check for redundant attributes
redundant_attributes_check="-std.concurrency,-std.digest.md,-std.digest.ripemd,-std.digest.sha,-std.internal.math.biguintcore,-std.math,-std.meta,-std.range,-std.regex.internal.ir,-std.uni,-std.windows.registry"
; Check variable, class, struct, interface, union, and function names against
; the Phobos style guide
style_check="+disabled"
-;style_check="-etc.c.curl,-etc.c.odbc.sqlext,-etc.c.odbc.sqltypes,-etc.c.odbc.sqlucode,-etc.c.sqlite3,-etc.c.zlib,-std.algorithm.comparison,-std.algorithm.internal,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.sorting,-std.array,-std.base64,-std.bitmanip,-std.c.linux.linux,-std.compiler,-std.container.array,-std.conv,-std.datetime.date,-std.datetime.interval,-std.datetime.systime,-std.digest,-std.digest.murmurhash,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bucketizer,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.null_allocator,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.segregator,-std.experimental.allocator.common,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.allocator.mmap_allocator,-std.experimental.checkedint,-std.experimental.typecons,-std.format,-std.functional,-std.getopt,-std.internal.digest.sha_SSSE3,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.test.dummyrange,-std.internal.unicode_tables,-std.json,-std.math,-std.meta,-std.numeric,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.primitives,-std.regex,-std.regex.internal.ir,-std.regex.internal.kickstart,-std.signals,-std.socket,-std.stdio,-std.string,-std.uni,-std.uri,-std.utf,-std.uuid,-std.variant,-std.zlib"
+;style_check="-etc.c.curl,-etc.c.odbc.sqlext,-etc.c.odbc.sqltypes,-etc.c.odbc.sqlucode,-etc.c.sqlite3,-etc.c.zlib,-std.algorithm.comparison,-std.algorithm.internal,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.sorting,-std.array,-std.base64,-std.bitmanip,-std.c.linux.linux,-std.compiler,-std.container.array,-std.conv,-std.datetime.date,-std.datetime.interval,-std.datetime.systime,-std.digest,-std.digest.murmurhash,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bucketizer,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.null_allocator,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.segregator,-std.experimental.allocator.common,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.allocator.mmap_allocator,-std.experimental.checkedint,-std.format,-std.functional,-std.getopt,-std.internal.digest.sha_SSSE3,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.test.dummyrange,-std.internal.unicode_tables,-std.json,-std.math,-std.meta,-std.numeric,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.primitives,-std.regex,-std.regex.internal.ir,-std.regex.internal.kickstart,-std.signals,-std.socket,-std.stdio,-std.string,-std.uni,-std.uri,-std.utf,-std.uuid,-std.variant,-std.zlib"
; Checks for undocumented public declarations
-undocumented_declaration_check="-etc.c.curl,-etc.c.odbc.sql,-etc.c.odbc.sqlext,-etc.c.odbc.sqltypes,-etc.c.odbc.sqlucode,-etc.c.sqlite3,-etc.c.zlib,-std.algorithm.sorting,-std.array,-std.ascii,-std.base64,-std.bitmanip,-std.c.linux.linux,-std.c.linux.socket,-std.c.osx.socket,-std.c.process,-std.compiler,-std.complex,-std.concurrency,-std.container,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.conv,-std.csv,-std.datetime,-std.datetime.date,-std.digest,-std.digest.hmac,-std.digest.md,-std.digest.murmurhash,-std.digest.ripemd,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bitmapped_block,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.quantizer,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.segregator,-std.experimental.allocator.building_blocks.stats_collector,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.checkedint,-std.logger.core,-std.experimental.typecons,-std.file,-std.format,-std.functional,-std.internal.digest.sha_SSSE3,-std.internal.math.biguintcore,-std.internal.math.biguintnoasm,-std.internal.math.biguintx86,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.test.dummyrange,-std.internal.test.uda,-std.internal.windows.advapi32,-std.json,-std.math,-std.mmfile,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.regex,-std.regex.internal.parser,-std.signals,-std.socket,-std.stdio,-std.string,-std.system,-std.traits,-std.uni,-std.utf,-std.variant,-std.windows.charset,-std.windows.registry,-std.windows.syserror,-std.xml,-std.zip,-std.zlib,-std.int128"
+undocumented_declaration_check="-etc.c.curl,-etc.c.odbc.sql,-etc.c.odbc.sqlext,-etc.c.odbc.sqltypes,-etc.c.odbc.sqlucode,-etc.c.sqlite3,-etc.c.zlib,-std.algorithm.sorting,-std.array,-std.ascii,-std.base64,-std.bitmanip,-std.c.linux.linux,-std.c.linux.socket,-std.c.osx.socket,-std.c.process,-std.compiler,-std.complex,-std.concurrency,-std.container,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.conv,-std.csv,-std.datetime,-std.datetime.date,-std.digest,-std.digest.hmac,-std.digest.md,-std.digest.murmurhash,-std.digest.ripemd,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bitmapped_block,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.quantizer,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.segregator,-std.experimental.allocator.building_blocks.stats_collector,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.checkedint,-std.logger.core,-std.file,-std.format,-std.functional,-std.internal.digest.sha_SSSE3,-std.internal.math.biguintcore,-std.internal.math.biguintnoasm,-std.internal.math.biguintx86,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.test.dummyrange,-std.internal.test.uda,-std.internal.windows.advapi32,-std.json,-std.math,-std.mmfile,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.regex,-std.regex.internal.parser,-std.signals,-std.socket,-std.stdio,-std.string,-std.system,-std.traits,-std.uni,-std.utf,-std.variant,-std.windows.charset,-std.windows.registry,-std.windows.syserror,-std.zip,-std.zlib,-std.int128"
; Checks for unused labels
unused_label_check="-std.conv,-std.internal.math.biguintx86,-std.regex.internal.thompson,-std.signals,-std.uni"
; Checks for unused function parameters
@@ -422,7 +417,6 @@ unused_parameter_check="-std.algorithm.comparison,\
-std.encoding,\
-std.experimental.allocator.common,\
-std.experimental.checkedint,\
--std.experimental.typecons,\
-std.format,\
-std.functional,\
-std.getopt,\
@@ -435,8 +429,7 @@ unused_parameter_check="-std.algorithm.comparison,\
-std.regex,\
-std.socket,\
-std.traits,\
--std.typecons,\
--std.xml"
+-std.typecons"
; Checks for unused variables
unused_variable_check="-std.algorithm.comparison,\
-std.array,\
@@ -475,7 +468,6 @@ unused_variable_check="-std.algorithm.comparison,\
-std.experimental.allocator.typed,\
-std.experimental.checkedint,\
-std.logger.core,\
--std.experimental.typecons,\
-std.file,\
-std.functional,\
-std.getopt,\
@@ -511,11 +503,10 @@ unused_variable_check="-std.algorithm.comparison,\
-std.uuid,\
-std.variant,\
-std.windows.registry,\
--std.xml,\
-std.zip,\
-std.zlib"
; Check for virtual calls in the class constructors
-vcall_in_ctor="-std.socket,-std.xml"
+vcall_in_ctor="-std.socket"
; Check for @trusted applied to a bigger scope than a single function
trust_too_much="-std.regex,-std.stdio,-std.uni,-std.internal.cstring"
; Checks for if statements whose 'then' block is the same as the 'else' block
diff --git a/CODEOWNERS b/CODEOWNERS
index 8a778208094..4327706ab2b 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -36,7 +36,6 @@ std/digest/* @jpf91
std/experimental/allocator/* @andralex @wilzbach @PetarKirov
std/experimental/checkedint/* @andralex
std/experimental/logger/* @burner
-# std/experimental/typecons.d
std/file.d @CyberShadow
# std/format.d
# std/functional.d
@@ -72,6 +71,5 @@ std/utf.d @jmdavis
std/uuid.d @jpf91
# std/variant.d
std/windows/* @CyberShadow
-# std/xml.d
std/zip.d @CyberShadow
std/zlib.d @CyberShadow
diff --git a/changelog/borrow_for_refcounted.dd b/changelog/borrow_for_refcounted.dd
new file mode 100644
index 00000000000..be55d2f8cfc
--- /dev/null
+++ b/changelog/borrow_for_refcounted.dd
@@ -0,0 +1,52 @@
+Added `SafeRefCounted`, that can be used in `@safe` with `-preview=dip1000`.
+
+`RefCounted` is only available for `@system` code, because of the possibility of
+escaping a reference to its payload past the end of its lifetime. a modified
+copy of it, `std.typecons.SafeRefCounted` has been added. Also added is a
+`borrow` function, that lets one safely access and modify the payload.
+`-preview=dip1000` prevents escaping a reference to it in `@safe` code.
+
+-------
+@safe pure nothrow void fun()
+{
+ import std.typecons;
+
+ auto rcInt = safeRefCounted(5);
+ assert(rcInt.borrow!(theInt => theInt) == 5);
+ auto sameInt = rcInt;
+ assert(sameInt.borrow!"a" == 5);
+
+ // using `ref` in the function
+ auto arr = [0, 1, 2, 3, 4, 5, 6];
+ sameInt.borrow!(ref (x) => arr[x]) = 10;
+ assert(arr == [0, 1, 2, 3, 4, 10, 6]);
+
+ // modifying the payload via an alias
+ sameInt.borrow!"a*=2";
+ assert(rcInt.borrow!"a" == 10);
+}
+-------
+
+Direct access to the payload unfortunately has to be `@system`, though. While
+`-dip1000` could prevent escaping the reference, it is possible to destroy the
+last reference before the end of it's scope:
+
+-------
+int destroyFirstAndUseLater()
+{
+ import std.typecons;
+
+ auto rc = SafeRefCounted!int(123);
+ int* ptr = &rc.refCountedPayload();
+ destroy(rc);
+ return *ptr; // Reads from freed memory. Don't do this.
+}
+-------
+
+As a side effect, this enabled us to make $(REF dirEntries, std, file) `@safe`
+with `-preview=dip1000`.
+
+Some member functions of `RefCounted` that are `@safe` are not so in
+`SafeRefCounted`. The `RefCounted` type and `refCounted` function are still
+available for the old behaviour. However, their main purpose is backwards
+compatibility. They are not recommended for new code.
diff --git a/changelog/remove-experimental-typecons.dd b/changelog/remove-experimental-typecons.dd
new file mode 100644
index 00000000000..4979dc2994f
--- /dev/null
+++ b/changelog/remove-experimental-typecons.dd
@@ -0,0 +1,4 @@
+std.experimental.typecons has been removed
+
+This was an attempt to update `std.typecons.wrap` with an implementation that could work with struct, but it did not go anywhere.
+See [this post on the forum](https://forum.dlang.org/post/ialskabybaxtqamgtwge@forum.dlang.org).
diff --git a/changelog/remove-std-digest-digest.dd b/changelog/remove-std-digest-digest.dd
new file mode 100644
index 00000000000..08af29bad2d
--- /dev/null
+++ b/changelog/remove-std-digest-digest.dd
@@ -0,0 +1,5 @@
+std.digest.digest has been removed
+
+This module was initially deprecated in 2.076.1, and has been empty since
+2.092.0 when all deprecated symbols were removed in favour of importing
+`std.digest` or its submodules instead.
diff --git a/changelog/remove-std-xml.dd b/changelog/remove-std-xml.dd
new file mode 100644
index 00000000000..ef491cb6e9b
--- /dev/null
+++ b/changelog/remove-std-xml.dd
@@ -0,0 +1,4 @@
+std.xml has been removed
+
+This module is considered out-dated and not up to Phobos' current standards.
+If you still need it, go to $(LINK https://github.com/DigitalMars/undeaD)
diff --git a/index.dd b/index.dd
index 45c248e1178..48122259670 100644
--- a/index.dd
+++ b/index.dd
@@ -70,10 +70,6 @@ $(BOOKTABLE ,
$(TDNW $(MREF std,json))
$(TD Read/write data in JSON format.)
)
- $(TR
- $(TDNW $(MREF std,xml))
- $(TD Read/write data in XML format.)
- )
$(TR
$(TDNW $(MREF std,zip))
$(TD Read/write data in the ZIP archive format.)
diff --git a/posix.mak b/posix.mak
index 1e6a3613c80..2ef5d459aea 100644
--- a/posix.mak
+++ b/posix.mak
@@ -216,8 +216,8 @@ PACKAGE_std = array ascii base64 bigint bitmanip checkedint compiler complex con
functional getopt int128 json mathspecial meta mmfile numeric \
outbuffer package parallelism path process random signals socket stdint \
stdio string sumtype system traits typecons \
- uri utf uuid variant xml zip zlib
-PACKAGE_std_experimental = checkedint typecons
+ uri utf uuid variant zip zlib
+PACKAGE_std_experimental = checkedint
PACKAGE_std_algorithm = comparison iteration mutation package searching setops \
sorting
PACKAGE_std_container = array binaryheap dlist package rbtree slist util
@@ -259,7 +259,6 @@ EXTRA_DOCUMENTABLES := $(EXTRA_MODULES_COMMON)
EXTRA_MODULES_INTERNAL := $(addprefix std/, \
algorithm/internal \
- digest/digest \
$(addprefix internal/, \
cstring memory digest/sha_SSSE3 \
$(addprefix math/, biguintcore biguintnoasm biguintx86 \
diff --git a/std/algorithm/comparison.d b/std/algorithm/comparison.d
index b810fbb9258..5ecb4f601a5 100644
--- a/std/algorithm/comparison.d
+++ b/std/algorithm/comparison.d
@@ -577,14 +577,24 @@ Returns:
and `T3` are different.
*/
T1 clamp(T1, T2, T3)(T1 val, T2 lower, T3 upper)
-if (is(typeof(val.lessThan(lower) ? lower : val.greaterThan(upper) ? upper : val) : T1))
+if (is(typeof(val.lessThan(lower) ? lower : val.greaterThan(upper) ? upper : val))
+ && (is(T2 : T1) && is(T3 : T1)))
+// cannot use :
+// `if (is(typeof(val.lessThan(lower) ? lower : val.greaterThan(upper) ? upper : val) : T1))
+// because of https://issues.dlang.org/show_bug.cgi?id=16235.
+// Once that is fixed, we can simply use the ternary in both the template constraint
+// and the template body
in
{
assert(!lower.greaterThan(upper), "Lower can't be greater than upper.");
}
do
{
- return val.lessThan(lower) ? lower : val.greaterThan(upper) ? upper : val;
+ if (val.lessThan(lower))
+ return lower;
+ else if (val.greaterThan(upper))
+ return upper;
+ return val;
}
///
@@ -637,6 +647,12 @@ do
assert(x.clamp(lo, hi).y == 42);
}
+// https://issues.dlang.org/show_bug.cgi?id=23268
+@safe pure nothrow @nogc unittest
+{
+ static assert(__traits(compiles, clamp(short.init, short.init, cast(const) short.init)));
+}
+
// cmp
/**********************************
Performs a lexicographical comparison on two
diff --git a/std/algorithm/iteration.d b/std/algorithm/iteration.d
index 300a8978fe5..9a365d59e2c 100644
--- a/std/algorithm/iteration.d
+++ b/std/algorithm/iteration.d
@@ -771,6 +771,23 @@ private struct MapResult(alias fun, Range)
assert(dd.length == 4);
}
+// Verify fix for: https://issues.dlang.org/show_bug.cgi?id=16034
+@safe unittest
+{
+ struct One
+ {
+ int entry = 1;
+ @disable this(this);
+ }
+
+ One[] ones = [One(), One()];
+
+ import std.algorithm.comparison : equal;
+
+ assert(ones.map!`a.entry + 1`.equal([2, 2]));
+}
+
+
@safe unittest
{
import std.algorithm.comparison : equal;
@@ -1798,7 +1815,7 @@ if (isInputRange!R)
assert(equal(g3, [ tuple(1, 2u), tuple(2, 2u) ]));
interface I {}
- class C : I { override size_t toHash() const nothrow @safe { return 0; } }
+ static class C : I { override size_t toHash() const nothrow @safe { return 0; } }
const C[] a4 = [new const C()];
auto g4 = a4.group!"a is b";
assert(g4.front[1] == 1);
@@ -2255,25 +2272,26 @@ if (isForwardRange!Range)
import std.algorithm.comparison : equal;
size_t popCount = 0;
- class RefFwdRange
+ static class RefFwdRange
{
int[] impl;
+ size_t* pcount;
@safe nothrow:
- this(int[] data) { impl = data; }
+ this(int[] data, size_t* pcount) { impl = data; this.pcount = pcount; }
@property bool empty() { return impl.empty; }
@property auto ref front() { return impl.front; }
void popFront()
{
impl.popFront();
- popCount++;
+ (*pcount)++;
}
- @property auto save() { return new RefFwdRange(impl); }
+ @property auto save() { return new RefFwdRange(impl, pcount); }
}
static assert(isForwardRange!RefFwdRange);
- auto testdata = new RefFwdRange([1, 3, 5, 2, 4, 7, 6, 8, 9]);
+ auto testdata = new RefFwdRange([1, 3, 5, 2, 4, 7, 6, 8, 9], &popCount);
auto groups = testdata.chunkBy!((a,b) => (a % 2) == (b % 2));
auto outerSave1 = groups.save;
@@ -6058,7 +6076,7 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool)
import std.algorithm.comparison : equal;
// Test by-reference separator
- class RefSep {
+ static class RefSep {
@safe:
string _impl;
this(string s) { _impl = s; }
@@ -7908,7 +7926,13 @@ if (isRandomAccessRange!Range && hasLength!Range)
_indices = iota(size_t(r.length)).array;
_empty = r.length == 0;
}
-
+ private this(size_t[] indices, size_t[] state, Range r, bool empty_)
+ {
+ _indices = indices;
+ _state = state;
+ _r = r;
+ _empty = empty_;
+ }
/// Returns: `true` if the range is empty, `false` otherwise.
@property bool empty() const pure nothrow @safe @nogc
{
@@ -7949,6 +7973,11 @@ if (isRandomAccessRange!Range && hasLength!Range)
next(2);
}
+ /// Returns: an independent copy of the permutations range.
+ auto save()
+ {
+ return typeof(this)(_indices.dup, _state.dup, _r.save, _empty);
+ }
}
///
@@ -7964,3 +7993,15 @@ if (isRandomAccessRange!Range && hasLength!Range)
[1, 2, 0],
[2, 1, 0]]));
}
+
+@safe unittest
+{
+ import std.algorithm.comparison : equal;
+ import std.range : ElementType;
+ import std.array : array;
+ auto p = [1, 2, 3].permutations;
+ auto x = p.save.front;
+ p.popFront;
+ auto y = p.front;
+ assert(x != y);
+}
diff --git a/std/algorithm/searching.d b/std/algorithm/searching.d
index 870b1b40219..15f7ca99e66 100644
--- a/std/algorithm/searching.d
+++ b/std/algorithm/searching.d
@@ -5002,7 +5002,7 @@ If set to `OpenRight.yes`, then the interval is open to the right
(last element is not included).
Otherwise if set to `OpenRight.no`, then the interval is closed to the right
-(last element included).
+including the entire sentinel.
*/
alias OpenRight = Flag!"openRight";
@@ -5052,6 +5052,7 @@ if (isInputRange!Range)
static if (!is(Sentinel == void))
private Sentinel _sentinel;
private OpenRight _openRight;
+ private bool _matchStarted;
private bool _done;
static if (!is(Sentinel == void))
@@ -5063,7 +5064,19 @@ if (isInputRange!Range)
_input = input;
_sentinel = sentinel;
_openRight = openRight;
- _done = _input.empty || openRight && predSatisfied();
+ static if (isInputRange!Sentinel)
+ {
+ _matchStarted = predSatisfied();
+ _done = _input.empty || _sentinel.empty || openRight && _matchStarted;
+ if (_matchStarted && !_done && !openRight)
+ {
+ _sentinel.popFront;
+ }
+ }
+ else
+ {
+ _done = _input.empty || openRight && predSatisfied();
+ }
}
private this(Range input, Sentinel sentinel, OpenRight openRight,
bool done)
@@ -5118,9 +5131,32 @@ if (isInputRange!Range)
assert(!empty, "Can not popFront of an empty Until");
if (!_openRight)
{
- _done = predSatisfied();
- _input.popFront();
- _done = _done || _input.empty;
+ static if (isInputRange!Sentinel)
+ {
+ _input.popFront();
+ _done = _input.empty || _sentinel.empty;
+ if (!_done)
+ {
+ if (_matchStarted)
+ {
+ _sentinel.popFront;
+ }
+ else
+ {
+ _matchStarted = predSatisfied();
+ if (_matchStarted)
+ {
+ _sentinel.popFront;
+ }
+ }
+ }
+ }
+ else
+ {
+ _done = predSatisfied();
+ _input.popFront();
+ _done = _done || _input.empty;
+ }
}
else
{
@@ -5212,3 +5248,33 @@ pure @safe unittest
assert(equal(r.save, "foo"));
}
}
+// https://issues.dlang.org/show_bug.cgi?id=14543
+pure @safe unittest
+{
+ import std.algorithm.comparison : equal;
+ import std.uni : toUpper;
+ assert("one two three".until("two").equal("one "));
+ assert("one two three".until("two", OpenRight.no).equal("one two"));
+
+ assert("one two three".until("two", No.openRight).equal("one two"));
+ assert("one two three".until("two", Yes.openRight).equal("one "));
+
+ assert("one two three".until('t', Yes.openRight).equal("one "));
+ assert("one two three".until("", Yes.openRight).equal(""));
+ assert("one two three".until("", No.openRight).equal(""));
+
+ assert("one two three".until("three", No.openRight).equal("one two three"));
+ assert("one two three".until("three", Yes.openRight).equal("one two "));
+
+ assert("one two three".until("one", No.openRight).equal("one"));
+ assert("one two three".until("one", Yes.openRight).equal(""));
+
+ assert("one two three".until("o", No.openRight).equal("o"));
+ assert("one two three".until("o", Yes.openRight).equal(""));
+
+ assert("one two three".until("", No.openRight).equal(""));
+ assert("one two three".until("", Yes.openRight).equal(""));
+
+ assert("one two three".until!((a,b)=>a.toUpper == b)("TWO", No.openRight).equal("one two"));
+}
+
diff --git a/std/algorithm/sorting.d b/std/algorithm/sorting.d
index ee68b234b15..4fc7ee9ac4c 100644
--- a/std/algorithm/sorting.d
+++ b/std/algorithm/sorting.d
@@ -1642,9 +1642,9 @@ private void multiSortImpl(Range, SwapStrategy ss, funs...)(Range r)
}
// https://issues.dlang.org/show_bug.cgi?id=16413 - @system comparison function
-@safe unittest
+@system unittest
{
- bool lt(int a, int b) { return a < b; } static @system
+ static @system bool lt(int a, int b) { return a < b; }
auto a = [2, 1];
a.multiSort!(lt, lt);
assert(a == [1, 2]);
diff --git a/std/base64.d b/std/base64.d
index d971dba1c71..2f7213b737b 100644
--- a/std/base64.d
+++ b/std/base64.d
@@ -922,7 +922,7 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=')
* length $(D_PARAM sourceLength).
*/
@safe
- pure nothrow size_t decodeLength(in size_t sourceLength)
+ pure @nogc nothrow size_t decodeLength(in size_t sourceLength)
{
static if (Padding == NoPadding)
return (sourceLength / 4) * 3 + (sourceLength % 4 < 2 ? 0 : sourceLength % 4 == 2 ? 1 : 2);
@@ -946,7 +946,7 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=')
// Used in decode contracts. Calculates the actual size the decoded
// result should have, taking into account trailing padding.
@safe
- pure nothrow private size_t realDecodeLength(R)(R source)
+ pure @nogc nothrow private size_t realDecodeLength(R)(R source)
{
auto expect = decodeLength(source.length);
static if (Padding != NoPadding)
diff --git a/std/compiler.d b/std/compiler.d
index 2f983c58fe0..4ea5bd72433 100644
--- a/std/compiler.d
+++ b/std/compiler.d
@@ -28,7 +28,7 @@ immutable
gnu = 2, /// GNU D Compiler (GDC)
llvm = 3, /// LLVM D Compiler (LDC)
dotNET = 4, /// D.NET
- sdc = 5, /// Stupid D Compiler (SDC)
+ sdc = 5, /// Snazzy D Compiler (SDC)
}
/// Which vendor produced this compiler.
diff --git a/std/concurrency.d b/std/concurrency.d
index fb383ae3f2e..bc53236b9b7 100644
--- a/std/concurrency.d
+++ b/std/concurrency.d
@@ -257,9 +257,12 @@ private
@property ref ThreadInfo thisInfo() nothrow
{
- if (scheduler is null)
+ import core.atomic : atomicLoad;
+
+ auto localScheduler = atomicLoad(scheduler);
+ if (localScheduler is null)
return ThreadInfo.thisInfo;
- return scheduler.thisInfo;
+ return localScheduler.thisInfo;
}
}
diff --git a/std/container/array.d b/std/container/array.d
index 08f9ead196e..ecc45996925 100644
--- a/std/container/array.d
+++ b/std/container/array.d
@@ -412,9 +412,9 @@ if (!is(immutable T == immutable bool))
.destroy(e);
static if (hasIndirections!T)
- GC.removeRange(_payload.ptr);
+ GC.removeRange(cast(void*) _payload.ptr);
- free(_payload.ptr);
+ free(cast(void*) _payload.ptr);
}
this(this) @disable;
@@ -489,14 +489,14 @@ if (!is(immutable T == immutable bool))
auto newPayload = newPayloadPtr[0 .. oldLength];
// copy old data over to new array
- memcpy(newPayload.ptr, _payload.ptr, T.sizeof * oldLength);
+ memcpy(cast(void*) newPayload.ptr, cast(void*) _payload.ptr, T.sizeof * oldLength);
// Zero out unused capacity to prevent gc from seeing false pointers
- memset(newPayload.ptr + oldLength,
+ memset( cast(void*) (newPayload.ptr + oldLength),
0,
(elements - oldLength) * T.sizeof);
- GC.addRange(newPayload.ptr, sz);
- GC.removeRange(_payload.ptr);
- free(_payload.ptr);
+ GC.addRange(cast(void*) newPayload.ptr, sz);
+ GC.removeRange(cast(void*) _payload.ptr);
+ free(cast(void*) _payload.ptr);
_payload = newPayload;
}
else
@@ -611,12 +611,17 @@ if (!is(immutable T == immutable bool))
return opEquals(rhs);
}
+ // fix https://issues.dlang.org/show_bug.cgi?23140
+ private alias Unshared(T) = T;
+ private alias Unshared(T: shared U, U) = U;
+
/// ditto
bool opEquals(ref const Array rhs) const
{
if (empty) return rhs.empty;
if (rhs.empty) return false;
- return _data._payload == rhs._data._payload;
+
+ return cast(Unshared!(T)[]) _data._payload == cast(Unshared!(T)[]) rhs._data._payload;
}
/**
@@ -1740,6 +1745,16 @@ if (!is(immutable T == immutable bool))
assertThrown!AssertError(array.length = 5);
}
+// https://issues.dlang.org/show_bug.cgi?id=23140
+@system unittest
+{
+ shared class C
+ {
+ }
+
+ Array!C ac;
+ ac = Array!C([new C]);
+}
////////////////////////////////////////////////////////////////////////////////
// Array!bool
////////////////////////////////////////////////////////////////////////////////
diff --git a/std/container/binaryheap.d b/std/container/binaryheap.d
index e763357a012..723630c89e2 100644
--- a/std/container/binaryheap.d
+++ b/std/container/binaryheap.d
@@ -89,6 +89,8 @@ if (isRandomAccessRange!(Store) || isRandomAccessRange!(typeof(Store.init[])))
Store _store;
size_t _length;
}
+ // TODO: migrate to use the SafeRefCounted. The problem is that some member
+ // functions here become @system with a naive switch.
private RefCounted!(Data, RefCountedAutoInitialize.no) _payload;
// Comparison predicate
private alias comp = binaryFun!(less);
diff --git a/std/container/dlist.d b/std/container/dlist.d
index 32d56ecc733..11e3883e5ee 100644
--- a/std/container/dlist.d
+++ b/std/container/dlist.d
@@ -111,7 +111,7 @@ private struct DRange
static assert(is(ElementType!DRange == BaseNode*));
}
-nothrow @safe pure:
+nothrow @safe @nogc pure:
private BaseNode* _first;
private BaseNode* _last;
diff --git a/std/container/rbtree.d b/std/container/rbtree.d
index 622dee4df2f..9bd8d27c065 100644
--- a/std/container/rbtree.d
+++ b/std/container/rbtree.d
@@ -2055,7 +2055,7 @@ if ( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init
}
//Combinations not in examples.
-@safe pure unittest
+@system pure unittest
{
auto rbt1 = redBlackTree!(true, string)("hello", "hello");
auto rbt2 = redBlackTree!((a, b){return a < b;}, double)(5.1, 2.3);
diff --git a/std/conv.d b/std/conv.d
index 9164e079865..0f660658920 100644
--- a/std/conv.d
+++ b/std/conv.d
@@ -4894,7 +4894,7 @@ if (isOctalLiteral(num))
template octal(alias decimalInteger)
if (is(typeof(decimalInteger)) && isIntegral!(typeof(decimalInteger)))
{
- enum octal = octal!(typeof(decimalInteger))(to!string(decimalInteger));
+ enum octal = convertToOctal(decimalInteger);
}
///
@@ -4910,6 +4910,19 @@ if (is(typeof(decimalInteger)) && isIntegral!(typeof(decimalInteger)))
auto d = octal!"0001_200_000";
}
+/*************************************
+ * Convert a decimal integer to an octal integer with the same digits.
+ * Params:
+ * i = integer to convert
+ * Returns:
+ * octal integer with the same type and same digits
+ */
+private T convertToOctal(T)(T i)
+{
+ assert((i % 10) < 8);
+ return i ? convertToOctal(i / 10) * 8 + i % 10 : 0;
+}
+
/*
Takes a string, num, which is an octal literal, and returns its
value, in the type T specified.
diff --git a/std/datetime/stopwatch.d b/std/datetime/stopwatch.d
index d8ecf7dbfbb..eedc0ea79c9 100644
--- a/std/datetime/stopwatch.d
+++ b/std/datetime/stopwatch.d
@@ -234,7 +234,7 @@ public:
/++
- Peek at the amount of time that the the StopWatch has been running.
+ Peek at the amount of time that the StopWatch has been running.
This does not include any time during which the StopWatch was stopped but
does include $(I all) of the time that it was running and not just the
diff --git a/std/datetime/timezone.d b/std/datetime/timezone.d
index 28255f4ef99..b23891808f5 100644
--- a/std/datetime/timezone.d
+++ b/std/datetime/timezone.d
@@ -3397,8 +3397,7 @@ struct TZConversions
TZConversions parseTZConversions(string windowsZonesXMLText) @safe pure
{
// This is a bit hacky, since it doesn't properly read XML, but it avoids
- // needing to pull in std.xml (which we're theoretically replacing at some
- // point anyway).
+ // needing to pull in an xml parsing module.
import std.algorithm.iteration : uniq;
import std.algorithm.searching : find;
import std.algorithm.sorting : sort;
diff --git a/std/digest/crc.d b/std/digest/crc.d
index 38563b14cd0..b7922bb03da 100644
--- a/std/digest/crc.d
+++ b/std/digest/crc.d
@@ -555,7 +555,7 @@ ubyte[8] crc64ECMAOf(T...)(T data)
}
/**
- * This is a convenience alias for $(REF digest, std,digest,digest) using the
+ * This is a convenience alias for $(REF digest, std,digest) using the
* CRC64-ISO implementation.
*
* Params:
@@ -611,7 +611,7 @@ alias CRC32Digest = WrapperDigest!CRC32;
* OOP API CRC64-ECMA implementation.
* See `std.digest` for differences between template and OOP API.
*
- * This is an alias for $(D $(REF WrapperDigest, std,digest,digest)!CRC64ECMA),
+ * This is an alias for $(D $(REF WrapperDigest, std,digest)!CRC64ECMA),
* see there for more information.
*/
alias CRC64ECMADigest = WrapperDigest!CRC64ECMA;
@@ -620,7 +620,7 @@ alias CRC64ECMADigest = WrapperDigest!CRC64ECMA;
* OOP API CRC64-ISO implementation.
* See `std.digest` for differences between template and OOP API.
*
- * This is an alias for $(D $(REF WrapperDigest, std,digest,digest)!CRC64ISO),
+ * This is an alias for $(D $(REF WrapperDigest, std,digest)!CRC64ISO),
* see there for more information.
*/
alias CRC64ISODigest = WrapperDigest!CRC64ISO;
@@ -628,7 +628,7 @@ alias CRC64ISODigest = WrapperDigest!CRC64ISO;
///
@safe unittest
{
- //Simple example, hashing a string using Digest.digest helper function
+ //Simple example, hashing a string using CRC32Digest.digest helper function
auto crc = new CRC32Digest();
ubyte[] hash = crc.digest("abc");
//Let's get a hash string
diff --git a/std/digest/digest.d b/std/digest/digest.d
deleted file mode 100644
index 01fdbd75e4b..00000000000
--- a/std/digest/digest.d
+++ /dev/null
@@ -1,3 +0,0 @@
-// @@@DEPRECATED_2.101@@@
-deprecated("import std.digest instead of std.digest.digest. std.digest.digest will be removed in 2.101")
-module std.digest.digest;
diff --git a/std/experimental/allocator/building_blocks/affix_allocator.d b/std/experimental/allocator/building_blocks/affix_allocator.d
index 1e3df91f95e..d0d0b7cb27c 100644
--- a/std/experimental/allocator/building_blocks/affix_allocator.d
+++ b/std/experimental/allocator/building_blocks/affix_allocator.d
@@ -521,9 +521,9 @@ version (StdUnittest)
@system unittest
{
- import std.experimental.allocator.building_blocks.region : Region;
+ import std.experimental.allocator.building_blocks.region : BorrowedRegion;
- auto a = AffixAllocator!(Region!(), uint)(Region!()(new ubyte[1024 * 64]));
+ auto a = AffixAllocator!(BorrowedRegion!(), uint)(BorrowedRegion!()(new ubyte[1024 * 64]));
auto b = a.allocate(42);
assert(b.length == 42);
// Test that expand infers from parent
diff --git a/std/experimental/allocator/building_blocks/aligned_block_list.d b/std/experimental/allocator/building_blocks/aligned_block_list.d
index 14c6de4696f..99768bc318e 100644
--- a/std/experimental/allocator/building_blocks/aligned_block_list.d
+++ b/std/experimental/allocator/building_blocks/aligned_block_list.d
@@ -526,7 +526,7 @@ shared struct SharedAlignedBlockList(Allocator, ParentAllocator, ulong theAlignm
///
@system unittest
{
- import std.experimental.allocator.building_blocks.region : SharedRegion;
+ import std.experimental.allocator.building_blocks.region : SharedBorrowedRegion;
import std.experimental.allocator.building_blocks.ascending_page_allocator : SharedAscendingPageAllocator;
import std.experimental.allocator.building_blocks.null_allocator : NullAllocator;
import core.thread : ThreadGroup;
@@ -536,11 +536,11 @@ shared struct SharedAlignedBlockList(Allocator, ParentAllocator, ulong theAlignm
enum maxIter = 10;
/*
- In this example we use 'SharedAlignedBlockList' together with 'SharedRegion',
- in order to create a fast, thread-safe allocator.
+ In this example we use 'SharedAlignedBlockList' together with
+ 'SharedBorrowedRegion', in order to create a fast, thread-safe allocator.
*/
alias SuperAllocator = SharedAlignedBlockList!(
- SharedRegion!(NullAllocator, 1),
+ SharedBorrowedRegion!(1),
SharedAscendingPageAllocator,
4096);
@@ -597,7 +597,7 @@ version (StdUnittest)
SpinLock lock = SpinLock(SpinLock.Contention.brief);
alias SuperAllocator = SharedAlignedBlockList!(
- SharedRegion!(NullAllocator, 1),
+ SharedBorrowedRegion!(1),
SharedAscendingPageAllocator,
1 << 16);
void[][totalAllocs] buf;
diff --git a/std/experimental/allocator/building_blocks/allocator_list.d b/std/experimental/allocator/building_blocks/allocator_list.d
index bcab16d6d8f..ca83785914e 100644
--- a/std/experimental/allocator/building_blocks/allocator_list.d
+++ b/std/experimental/allocator/building_blocks/allocator_list.d
@@ -679,8 +679,8 @@ version (Posix) @system unittest
{
// Create an allocator based upon 4MB regions, fetched from the GC heap.
import std.algorithm.comparison : max;
- import std.experimental.allocator.building_blocks.region : Region;
- AllocatorList!((n) => Region!()(new ubyte[max(n, 1024 * 4096)])) a;
+ import std.experimental.allocator.building_blocks.region : BorrowedRegion;
+ AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a;
auto b1 = a.alignedAllocate(1024 * 8192, 1024);
assert(b1 !is null); // still works due to overdimensioning
assert(b1.length == 1024 * 8192);
@@ -707,8 +707,8 @@ version (Posix) @system unittest
// Create an allocator based upon 4MB regions, fetched from the GC heap.
import std.algorithm.comparison : max;
- import std.experimental.allocator.building_blocks.region : Region;
- AllocatorList!((n) => Region!()(new ubyte[max(n, 1024 * 4096)])) a;
+ import std.experimental.allocator.building_blocks.region : BorrowedRegion;
+ AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a;
auto b1 = a.alignedAllocate(0, 1);
assert(b1 is null);
@@ -728,8 +728,8 @@ version (Posix) @system unittest
// Create an allocator based upon 4MB regions, fetched from the GC heap.
import std.algorithm.comparison : max;
- import std.experimental.allocator.building_blocks.region : Region;
- AllocatorList!((n) => Region!()(new ubyte[max(n, 1024 * 4096)])) a;
+ import std.experimental.allocator.building_blocks.region : BorrowedRegion;
+ AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a;
auto b0 = a.alignedAllocate(1, 1024);
assert(b0.length == 1);
assert(b0.ptr.alignedAt(1024));
@@ -765,8 +765,8 @@ version (Posix) @system unittest
{
// Create an allocator based upon 4MB regions, fetched from the GC heap.
import std.algorithm.comparison : max;
- import std.experimental.allocator.building_blocks.region : Region;
- AllocatorList!((n) => Region!()(new ubyte[max(n, 1024 * 4096)])) a;
+ import std.experimental.allocator.building_blocks.region : BorrowedRegion;
+ AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a;
auto b1 = a.allocate(1024 * 8192);
assert(b1 !is null); // still works due to overdimensioning
b1 = a.allocate(1024 * 10);
@@ -779,10 +779,10 @@ version (Posix) @system unittest
@system unittest
{
import std.algorithm.comparison : max;
- import std.experimental.allocator.building_blocks.region : Region;
+ import std.experimental.allocator.building_blocks.region : BorrowedRegion;
import std.experimental.allocator.mallocator : Mallocator;
import std.typecons : Ternary;
- AllocatorList!((n) => Region!()(new ubyte[max(n, 1024 * 4096)]), Mallocator) a;
+ AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)]), Mallocator) a;
auto b1 = a.allocate(1024 * 8192);
assert(b1 !is null);
b1 = a.allocate(1024 * 10);
diff --git a/std/experimental/allocator/building_blocks/fallback_allocator.d b/std/experimental/allocator/building_blocks/fallback_allocator.d
index b413d738dab..3990418c3cf 100644
--- a/std/experimental/allocator/building_blocks/fallback_allocator.d
+++ b/std/experimental/allocator/building_blocks/fallback_allocator.d
@@ -342,12 +342,12 @@ struct FallbackAllocator(Primary, Fallback)
@system unittest
{
- import std.experimental.allocator.building_blocks.region : Region;
+ import std.experimental.allocator.building_blocks.region : BorrowedRegion;
import std.typecons : Ternary;
- auto a = FallbackAllocator!(Region!(), Region!())(
- Region!()(new ubyte[4096 * 1024]),
- Region!()(new ubyte[4096 * 1024]));
+ auto a = FallbackAllocator!(BorrowedRegion!(), BorrowedRegion!())(
+ BorrowedRegion!()(new ubyte[4096 * 1024]),
+ BorrowedRegion!()(new ubyte[4096 * 1024]));
auto b = a.alignedAllocate(42, 8);
assert(b.length == 42);
@@ -506,11 +506,11 @@ version (StdUnittest)
@system unittest
{
- import std.experimental.allocator.building_blocks.region : Region;
+ import std.experimental.allocator.building_blocks.region : BorrowedRegion;
import std.typecons : Ternary;
- alias A = FallbackAllocator!(Region!(), Region!());
- auto a = A(Region!()(new ubyte[16_384]), Region!()(new ubyte[16_384]));
+ alias A = FallbackAllocator!(BorrowedRegion!(), BorrowedRegion!());
+ auto a = A(BorrowedRegion!()(new ubyte[16_384]), BorrowedRegion!()(new ubyte[16_384]));
auto b = a.allocate(42);
assert(b.length == 42);
diff --git a/std/experimental/allocator/building_blocks/free_list.d b/std/experimental/allocator/building_blocks/free_list.d
index 7055d666fb1..d2b32099226 100644
--- a/std/experimental/allocator/building_blocks/free_list.d
+++ b/std/experimental/allocator/building_blocks/free_list.d
@@ -486,9 +486,9 @@ struct FreeList(ParentAllocator,
// Test that deallocateAll infers from parent
@system unittest
{
- import std.experimental.allocator.building_blocks.region : Region;
+ import std.experimental.allocator.building_blocks.region : BorrowedRegion;
- auto fl = FreeList!(Region!(), 0, 16)(Region!()(new ubyte[1024 * 64]));
+ auto fl = FreeList!(BorrowedRegion!(), 0, 16)(BorrowedRegion!()(new ubyte[1024 * 64]));
auto b = fl.allocate(42);
assert(b.length == 42);
assert((() pure nothrow @safe @nogc => fl.expand(b, 48))());
diff --git a/std/experimental/allocator/building_blocks/free_tree.d b/std/experimental/allocator/building_blocks/free_tree.d
index bd4bb9511ea..fe59e2650a1 100644
--- a/std/experimental/allocator/building_blocks/free_tree.d
+++ b/std/experimental/allocator/building_blocks/free_tree.d
@@ -502,9 +502,9 @@ version (StdUnittest)
@system unittest
{
- import std.experimental.allocator.building_blocks.region : Region;
+ import std.experimental.allocator.building_blocks.region : BorrowedRegion;
- auto a = FreeTree!(Region!())(Region!()(new ubyte[1024 * 64]));
+ auto a = FreeTree!(BorrowedRegion!())(BorrowedRegion!()(new ubyte[1024 * 64]));
auto b = a.allocate(42);
assert(b.length == 42);
assert((() pure nothrow @safe @nogc => a.expand(b, 22))());
diff --git a/std/experimental/allocator/building_blocks/quantizer.d b/std/experimental/allocator/building_blocks/quantizer.d
index 762b3797b41..3334a86fd84 100644
--- a/std/experimental/allocator/building_blocks/quantizer.d
+++ b/std/experimental/allocator/building_blocks/quantizer.d
@@ -315,14 +315,14 @@ version (StdUnittest)
version (StdUnittest)
@system unittest
{
- import std.experimental.allocator.building_blocks.region : Region;
+ import std.experimental.allocator.building_blocks.region : BorrowedRegion;
import std.typecons : Ternary;
- alias MyAlloc = Quantizer!(Region!(),
+ alias MyAlloc = Quantizer!(BorrowedRegion!(),
(size_t n) => n.roundUpToMultipleOf(64));
- testAllocator!(() => MyAlloc(Region!()(new ubyte[1024 * 64])));
+ testAllocator!(() => MyAlloc(BorrowedRegion!()(new ubyte[1024 * 64])));
- auto a = MyAlloc(Region!()(new ubyte[1024 * 64]));
+ auto a = MyAlloc(BorrowedRegion!()(new ubyte[1024 * 64]));
// Check that empty inherits from parent
assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.yes);
auto b = a.allocate(42);
diff --git a/std/experimental/allocator/building_blocks/region.d b/std/experimental/allocator/building_blocks/region.d
index be0d274633c..8c397849f43 100644
--- a/std/experimental/allocator/building_blocks/region.d
+++ b/std/experimental/allocator/building_blocks/region.d
@@ -29,8 +29,7 @@ the store and the limits. One allocation entails rounding up the allocation
size for alignment purposes, bumping the current pointer, and comparing it
against the limit.
-If `ParentAllocator` is different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator), `Region`
-deallocates the chunk of memory during destruction.
+`Region` deallocates the chunk of memory during destruction.
The `minAlign` parameter establishes alignment. If $(D minAlign > 1), the
sizes of all allocation requests are rounded up to a multiple of `minAlign`.
@@ -38,7 +37,7 @@ Applications aiming at maximum speed may want to choose $(D minAlign = 1) and
control alignment externally.
*/
-struct Region(ParentAllocator = NullAllocator,
+struct Region(ParentAllocator,
uint minAlign = platformAlignment,
Flag!"growDownwards" growDownwards = No.growDownwards)
{
@@ -63,50 +62,42 @@ struct Region(ParentAllocator = NullAllocator,
alias parent = ParentAllocator.instance;
}
- private void* _current, _begin, _end;
+ private BorrowedRegion!(minAlign, growDownwards) _impl;
private void* roundedBegin() const pure nothrow @trusted @nogc
{
- return cast(void*) roundUpToAlignment(cast(size_t) _begin, alignment);
+ return _impl.roundedBegin;
}
private void* roundedEnd() const pure nothrow @trusted @nogc
{
- return cast(void*) roundDownToAlignment(cast(size_t) _end, alignment);
+ return _impl.roundedEnd;
}
/**
Constructs a region backed by a user-provided store.
- Assumes the memory was allocated with `ParentAllocator`
- (if different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator)).
+ Assumes the memory was allocated with `ParentAllocator`.
Params:
- store = User-provided store backing up the region. If $(D
- ParentAllocator) is different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator), memory is assumed to
- have been allocated with `ParentAllocator`.
- n = Bytes to allocate using `ParentAllocator`. This constructor is only
- defined If `ParentAllocator` is different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator). If
- `parent.allocate(n)` returns `null`, the region will be initialized
- as empty (correctly initialized but unable to allocate).
+ store = User-provided store backing up the region. Assumed to have been
+ allocated with `ParentAllocator`.
+ n = Bytes to allocate using `ParentAllocator`. If `parent.allocate(n)`
+ returns `null`, the region will be initialized as empty (correctly
+ initialized but unable to allocate).
*/
this(ubyte[] store) pure nothrow @nogc
{
- _begin = store.ptr;
- _end = store.ptr + store.length;
- static if (growDownwards)
- _current = roundedEnd();
- else
- _current = roundedBegin();
+ _impl = store;
}
/// Ditto
- static if (!is(ParentAllocator == NullAllocator) && !stateSize!ParentAllocator)
+ static if (!stateSize!ParentAllocator)
this(size_t n)
{
this(cast(ubyte[]) (parent.allocate(n.roundUpToAlignment(alignment))));
}
/// Ditto
- static if (!is(ParentAllocator == NullAllocator) && stateSize!ParentAllocator)
+ static if (stateSize!ParentAllocator)
this(ParentAllocator parent, size_t n)
{
this.parent = parent;
@@ -119,15 +110,13 @@ struct Region(ParentAllocator = NullAllocator,
*/
/**
- If `ParentAllocator` is not $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator) and defines `deallocate`,
- the region defines a destructor that uses `ParentAllocator.deallocate` to free the
- memory chunk.
+ If `ParentAllocator` defines `deallocate`, the region defines a destructor
+ that uses `ParentAllocator.deallocate` to free the memory chunk.
*/
- static if (!is(ParentAllocator == NullAllocator)
- && hasMember!(ParentAllocator, "deallocate"))
+ static if (hasMember!(ParentAllocator, "deallocate"))
~this()
{
- parent.deallocate(_begin[0 .. _end - _begin]);
+ with (_impl) parent.deallocate(_begin[0 .. _end - _begin]);
}
/**
@@ -135,7 +124,7 @@ struct Region(ParentAllocator = NullAllocator,
*/
size_t goodAllocSize(size_t n) const pure nothrow @safe @nogc
{
- return n.roundUpToAlignment(alignment);
+ return _impl.goodAllocSize(n);
}
/**
@@ -156,24 +145,7 @@ struct Region(ParentAllocator = NullAllocator,
*/
void[] allocate(size_t n) pure nothrow @trusted @nogc
{
- const rounded = goodAllocSize(n);
- if (n == 0 || rounded < n || available < rounded) return null;
-
- static if (growDownwards)
- {
- assert(available >= rounded);
- auto result = (_current - rounded)[0 .. n];
- assert(result.ptr >= _begin);
- _current = result.ptr;
- assert(owns(result) == Ternary.yes);
- }
- else
- {
- auto result = _current[0 .. n];
- _current += rounded;
- }
-
- return result;
+ return _impl.allocate(n);
}
/**
@@ -188,57 +160,13 @@ struct Region(ParentAllocator = NullAllocator,
*/
void[] alignedAllocate(size_t n, uint a) pure nothrow @trusted @nogc
{
- import std.math.traits : isPowerOf2;
- assert(a.isPowerOf2);
-
- const rounded = goodAllocSize(n);
- if (n == 0 || rounded < n || available < rounded) return null;
-
- static if (growDownwards)
- {
- auto tmpCurrent = _current - rounded;
- auto result = tmpCurrent.alignDownTo(a);
- if (result <= tmpCurrent && result >= _begin)
- {
- _current = result;
- return cast(void[]) result[0 .. n];
- }
- }
- else
- {
- // Just bump the pointer to the next good allocation
- auto newCurrent = _current.alignUpTo(a);
- if (newCurrent < _current || newCurrent > _end)
- return null;
-
- auto save = _current;
- _current = newCurrent;
- auto result = allocate(n);
- if (result.ptr)
- {
- assert(result.length == n);
- return result;
- }
- // Failed, rollback
- _current = save;
- }
- return null;
+ return _impl.alignedAllocate(n, a);
}
/// Allocates and returns all memory available to this region.
void[] allocateAll() pure nothrow @trusted @nogc
{
- static if (growDownwards)
- {
- auto result = _begin[0 .. available];
- _current = _begin;
- }
- else
- {
- auto result = _current[0 .. available];
- _current = _end;
- }
- return result;
+ return _impl.allocateAll;
}
/**
@@ -249,25 +177,7 @@ struct Region(ParentAllocator = NullAllocator,
static if (growDownwards == No.growDownwards)
bool expand(ref void[] b, size_t delta) pure nothrow @safe @nogc
{
- assert(owns(b) == Ternary.yes || b is null);
- assert((() @trusted => b.ptr + b.length <= _current)() || b is null);
- if (b is null || delta == 0) return delta == 0;
- auto newLength = b.length + delta;
- if ((() @trusted => _current < b.ptr + b.length + alignment)())
- {
- immutable currentGoodSize = this.goodAllocSize(b.length);
- immutable newGoodSize = this.goodAllocSize(newLength);
- immutable goodDelta = newGoodSize - currentGoodSize;
- // This was the last allocation! Allocate some more and we're done.
- if (goodDelta == 0
- || (() @trusted => allocate(goodDelta).length == goodDelta)())
- {
- b = (() @trusted => b.ptr[0 .. newLength])();
- assert((() @trusted => _current < b.ptr + b.length + alignment)());
- return true;
- }
- }
- return false;
+ return _impl.expand(b, delta);
}
/**
@@ -281,26 +191,7 @@ struct Region(ParentAllocator = NullAllocator,
*/
bool deallocate(void[] b) pure nothrow @nogc
{
- assert(owns(b) == Ternary.yes || b.ptr is null);
- auto rounded = goodAllocSize(b.length);
- static if (growDownwards)
- {
- if (b.ptr == _current)
- {
- _current += rounded;
- return true;
- }
- }
- else
- {
- if (b.ptr + rounded == _current)
- {
- assert(b.ptr !is null || _current is null);
- _current = b.ptr;
- return true;
- }
- }
- return false;
+ return _impl.deallocate(b);
}
/**
@@ -309,15 +200,7 @@ struct Region(ParentAllocator = NullAllocator,
*/
bool deallocateAll() pure nothrow @nogc
{
- static if (growDownwards)
- {
- _current = roundedEnd();
- }
- else
- {
- _current = roundedBegin();
- }
- return true;
+ return _impl.deallocateAll;
}
/**
@@ -332,7 +215,7 @@ struct Region(ParentAllocator = NullAllocator,
*/
Ternary owns(const void[] b) const pure nothrow @trusted @nogc
{
- return Ternary(b && (&b[0] >= _begin) && (&b[0] + b.length <= _end));
+ return _impl.owns(b);
}
/**
@@ -341,23 +224,13 @@ struct Region(ParentAllocator = NullAllocator,
*/
Ternary empty() const pure nothrow @safe @nogc
{
- static if (growDownwards)
- return Ternary(_current == roundedEnd());
- else
- return Ternary(_current == roundedBegin());
+ return _impl.empty;
}
/// Nonstandard property that returns bytes available for allocation.
size_t available() const @safe pure nothrow @nogc
{
- static if (growDownwards)
- {
- return _current - _begin;
- }
- else
- {
- return _end - _current;
- }
+ return _impl.available;
}
}
@@ -414,17 +287,6 @@ struct Region(ParentAllocator = NullAllocator,
testAlloc(sharedReg);
}
-@system nothrow @nogc unittest
-{
- import std.experimental.allocator.mallocator : AlignedMallocator;
- import std.typecons : Ternary;
-
- ubyte[] buf = cast(ubyte[]) AlignedMallocator.instance.alignedAllocate(64, 64);
- auto reg = Region!(NullAllocator, 64, Yes.growDownwards)(buf);
- assert(reg.alignedAllocate(10, 32).length == 10);
- assert(!reg.available);
-}
-
@system nothrow @nogc unittest
{
// test 'this(ubyte[] store)' constructed regions properly clean up
@@ -491,107 +353,428 @@ version (StdUnittest)
}
/**
+A `BorrowedRegion` allocates directly from a user-provided block of memory.
-`InSituRegion` is a convenient region that carries its storage within itself
-(in the form of a statically-sized array).
-
-The first template argument is the size of the region and the second is the
-needed alignment. Depending on the alignment requested and platform details,
-the actual available storage may be smaller than the compile-time parameter. To
-make sure that at least `n` bytes are available in the region, use
-$(D InSituRegion!(n + a - 1, a)).
-
-Given that the most frequent use of `InSituRegion` is as a stack allocator, it
-allocates starting at the end on systems where stack grows downwards, such that
-hot memory is used first.
+Unlike a `Region`, a `BorrowedRegion` does not own the memory it allocates from
+and will not deallocate that memory upon destruction. Instead, it is the user's
+responsibility to ensure that the memory is properly disposed of.
+In all other respects, a `BorrowedRegion` behaves exactly like a `Region`.
*/
-struct InSituRegion(size_t size, size_t minAlign = platformAlignment)
+struct BorrowedRegion(uint minAlign = platformAlignment,
+ Flag!"growDownwards" growDownwards = No.growDownwards)
{
- import std.algorithm.comparison : max;
- import std.conv : to;
- import std.traits : hasMember;
- import std.typecons : Ternary;
-
static assert(minAlign.isGoodStaticAlignment);
- static assert(size >= minAlign);
- version (X86) enum growDownwards = Yes.growDownwards;
- else version (X86_64) enum growDownwards = Yes.growDownwards;
- else version (ARM) enum growDownwards = Yes.growDownwards;
- else version (AArch64) enum growDownwards = Yes.growDownwards;
- else version (HPPA) enum growDownwards = No.growDownwards;
- else version (PPC) enum growDownwards = Yes.growDownwards;
- else version (PPC64) enum growDownwards = Yes.growDownwards;
- else version (RISCV32) enum growDownwards = Yes.growDownwards;
- else version (RISCV64) enum growDownwards = Yes.growDownwards;
- else version (MIPS32) enum growDownwards = Yes.growDownwards;
- else version (MIPS64) enum growDownwards = Yes.growDownwards;
- else version (SPARC) enum growDownwards = Yes.growDownwards;
- else version (SPARC64) enum growDownwards = Yes.growDownwards;
- else version (SystemZ) enum growDownwards = Yes.growDownwards;
- else static assert(0, "Dunno how the stack grows on this architecture.");
+ import std.typecons : Ternary;
- @disable this(this);
+ // state
+ private void* _current, _begin, _end;
- // state {
- private Region!(NullAllocator, minAlign, growDownwards) _impl;
- union
+ private void* roundedBegin() const pure nothrow @trusted @nogc
{
- private ubyte[size] _store = void;
- private double _forAlignmentOnly1;
+ return cast(void*) roundUpToAlignment(cast(size_t) _begin, alignment);
+ }
+
+ private void* roundedEnd() const pure nothrow @trusted @nogc
+ {
+ return cast(void*) roundDownToAlignment(cast(size_t) _end, alignment);
}
- // }
/**
- An alias for `minAlign`, which must be a valid alignment (nonzero power
- of 2). The start of the region and all allocation requests will be rounded
- up to a multiple of the alignment.
+ Constructs a region backed by a user-provided store.
- ----
- InSituRegion!(4096) a1;
- assert(a1.alignment == platformAlignment);
- InSituRegion!(4096, 64) a2;
- assert(a2.alignment == 64);
- ----
+ Params:
+ store = User-provided store backing up the region.
*/
- alias alignment = minAlign;
-
- private void lazyInit()
+ this(ubyte[] store) pure nothrow @nogc
{
- assert(!_impl._current);
- _impl = typeof(_impl)(_store);
- assert(_impl._current.alignedAt(alignment));
+ _begin = store.ptr;
+ _end = store.ptr + store.length;
+ static if (growDownwards)
+ _current = roundedEnd();
+ else
+ _current = roundedBegin();
}
+ /*
+ TODO: The postblit of `BorrowedRegion` should be disabled because such objects
+ should not be copied around naively.
+ */
+
/**
- Allocates `bytes` and returns them, or `null` if the region cannot
- accommodate the request. For efficiency reasons, if $(D bytes == 0) the
- function returns an empty non-null slice.
+ Rounds the given size to a multiple of the `alignment`
*/
- void[] allocate(size_t n)
+ size_t goodAllocSize(size_t n) const pure nothrow @safe @nogc
{
- // Fast path
- entry:
- auto result = _impl.allocate(n);
- if (result.length == n) return result;
- // Slow path
- if (_impl._current) return null; // no more room
- lazyInit;
- assert(_impl._current);
- goto entry;
+ return n.roundUpToAlignment(alignment);
}
/**
- As above, but the memory allocated is aligned at `a` bytes.
+ Alignment offered.
*/
- void[] alignedAllocate(size_t n, uint a)
- {
- // Fast path
- entry:
- auto result = _impl.alignedAllocate(n, a);
- if (result.length == n) return result;
- // Slow path
+ alias alignment = minAlign;
+
+ /**
+ Allocates `n` bytes of memory. The shortest path involves an alignment
+ adjustment (if $(D alignment > 1)), an increment, and a comparison.
+
+ Params:
+ n = number of bytes to allocate
+
+ Returns:
+ A properly-aligned buffer of size `n` or `null` if request could not
+ be satisfied.
+ */
+ void[] allocate(size_t n) pure nothrow @trusted @nogc
+ {
+ const rounded = goodAllocSize(n);
+ if (n == 0 || rounded < n || available < rounded) return null;
+
+ static if (growDownwards)
+ {
+ assert(available >= rounded);
+ auto result = (_current - rounded)[0 .. n];
+ assert(result.ptr >= _begin);
+ _current = result.ptr;
+ assert(owns(result) == Ternary.yes);
+ }
+ else
+ {
+ auto result = _current[0 .. n];
+ _current += rounded;
+ }
+
+ return result;
+ }
+
+ /**
+ Allocates `n` bytes of memory aligned at alignment `a`.
+
+ Params:
+ n = number of bytes to allocate
+ a = alignment for the allocated block
+
+ Returns:
+ Either a suitable block of `n` bytes aligned at `a`, or `null`.
+ */
+ void[] alignedAllocate(size_t n, uint a) pure nothrow @trusted @nogc
+ {
+ import std.math.traits : isPowerOf2;
+ assert(a.isPowerOf2);
+
+ const rounded = goodAllocSize(n);
+ if (n == 0 || rounded < n || available < rounded) return null;
+
+ static if (growDownwards)
+ {
+ auto tmpCurrent = _current - rounded;
+ auto result = tmpCurrent.alignDownTo(a);
+ if (result <= tmpCurrent && result >= _begin)
+ {
+ _current = result;
+ return cast(void[]) result[0 .. n];
+ }
+ }
+ else
+ {
+ // Just bump the pointer to the next good allocation
+ auto newCurrent = _current.alignUpTo(a);
+ if (newCurrent < _current || newCurrent > _end)
+ return null;
+
+ auto save = _current;
+ _current = newCurrent;
+ auto result = allocate(n);
+ if (result.ptr)
+ {
+ assert(result.length == n);
+ return result;
+ }
+ // Failed, rollback
+ _current = save;
+ }
+ return null;
+ }
+
+ /// Allocates and returns all memory available to this region.
+ void[] allocateAll() pure nothrow @trusted @nogc
+ {
+ static if (growDownwards)
+ {
+ auto result = _begin[0 .. available];
+ _current = _begin;
+ }
+ else
+ {
+ auto result = _current[0 .. available];
+ _current = _end;
+ }
+ return result;
+ }
+
+ /**
+ Expands an allocated block in place. Expansion will succeed only if the
+ block is the last allocated. Defined only if `growDownwards` is
+ `No.growDownwards`.
+ */
+ static if (growDownwards == No.growDownwards)
+ bool expand(ref void[] b, size_t delta) pure nothrow @safe @nogc
+ {
+ assert(owns(b) == Ternary.yes || b is null);
+ assert((() @trusted => b.ptr + b.length <= _current)() || b is null);
+ if (b is null || delta == 0) return delta == 0;
+ auto newLength = b.length + delta;
+ if ((() @trusted => _current < b.ptr + b.length + alignment)())
+ {
+ immutable currentGoodSize = this.goodAllocSize(b.length);
+ immutable newGoodSize = this.goodAllocSize(newLength);
+ immutable goodDelta = newGoodSize - currentGoodSize;
+ // This was the last allocation! Allocate some more and we're done.
+ if (goodDelta == 0
+ || (() @trusted => allocate(goodDelta).length == goodDelta)())
+ {
+ b = (() @trusted => b.ptr[0 .. newLength])();
+ assert((() @trusted => _current < b.ptr + b.length + alignment)());
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ Deallocates `b`. This works only if `b` was obtained as the last call
+ to `allocate`; otherwise (i.e. another allocation has occurred since) it
+ does nothing.
+
+ Params:
+ b = Block previously obtained by a call to `allocate` against this
+ allocator (`null` is allowed).
+ */
+ bool deallocate(void[] b) pure nothrow @nogc
+ {
+ assert(owns(b) == Ternary.yes || b.ptr is null);
+ auto rounded = goodAllocSize(b.length);
+ static if (growDownwards)
+ {
+ if (b.ptr == _current)
+ {
+ _current += rounded;
+ return true;
+ }
+ }
+ else
+ {
+ if (b.ptr + rounded == _current)
+ {
+ assert(b.ptr !is null || _current is null);
+ _current = b.ptr;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ Deallocates all memory allocated by this region, which can be subsequently
+ reused for new allocations.
+ */
+ bool deallocateAll() pure nothrow @nogc
+ {
+ static if (growDownwards)
+ {
+ _current = roundedEnd();
+ }
+ else
+ {
+ _current = roundedBegin();
+ }
+ return true;
+ }
+
+ /**
+ Queries whether `b` has been allocated with this region.
+
+ Params:
+ b = Arbitrary block of memory (`null` is allowed; `owns(null)` returns
+ `false`).
+
+ Returns:
+ `true` if `b` has been allocated with this region, `false` otherwise.
+ */
+ Ternary owns(const void[] b) const pure nothrow @trusted @nogc
+ {
+ return Ternary(b && (&b[0] >= _begin) && (&b[0] + b.length <= _end));
+ }
+
+ /**
+ Returns `Ternary.yes` if no memory has been allocated in this region,
+ `Ternary.no` otherwise. (Never returns `Ternary.unknown`.)
+ */
+ Ternary empty() const pure nothrow @safe @nogc
+ {
+ static if (growDownwards)
+ return Ternary(_current == roundedEnd());
+ else
+ return Ternary(_current == roundedBegin());
+ }
+
+ /// Nonstandard property that returns bytes available for allocation.
+ size_t available() const @safe pure nothrow @nogc
+ {
+ static if (growDownwards)
+ {
+ return _current - _begin;
+ }
+ else
+ {
+ return _end - _current;
+ }
+ }
+}
+
+///
+@system nothrow @nogc unittest
+{
+ import std.typecons : Ternary;
+
+ ubyte[1024] store;
+ auto myRegion = BorrowedRegion!(1)(store[]);
+
+ assert(myRegion.empty == Ternary.yes);
+ assert(myRegion.available == store.length);
+
+ void[] b = myRegion.allocate(101);
+
+ assert(b.length == 101);
+ assert(myRegion.empty == Ternary.no);
+ assert(myRegion.owns(b) == Ternary.yes);
+ assert(myRegion.available == store.length - b.length);
+
+ void[] b2 = myRegion.allocate(256);
+
+ // Can only free the most recent allocation
+ assert(myRegion.deallocate(b) == false);
+ assert(myRegion.deallocate(b2) == true);
+
+ myRegion.deallocateAll();
+
+ assert(myRegion.empty == Ternary.yes);
+}
+
+@system nothrow @nogc unittest
+{
+ import std.experimental.allocator.mallocator : AlignedMallocator;
+ import std.typecons : Ternary;
+
+ ubyte[] buf = cast(ubyte[]) AlignedMallocator.instance.alignedAllocate(64, 64);
+ auto reg = BorrowedRegion!(64, Yes.growDownwards)(buf);
+ assert(reg.alignedAllocate(10, 32).length == 10);
+ assert(!reg.available);
+}
+
+/**
+
+`InSituRegion` is a convenient region that carries its storage within itself
+(in the form of a statically-sized array).
+
+The first template argument is the size of the region and the second is the
+needed alignment. Depending on the alignment requested and platform details,
+the actual available storage may be smaller than the compile-time parameter. To
+make sure that at least `n` bytes are available in the region, use
+$(D InSituRegion!(n + a - 1, a)).
+
+Given that the most frequent use of `InSituRegion` is as a stack allocator, it
+allocates starting at the end on systems where stack grows downwards, such that
+hot memory is used first.
+
+*/
+struct InSituRegion(size_t size, size_t minAlign = platformAlignment)
+{
+ import std.algorithm.comparison : max;
+ import std.conv : to;
+ import std.traits : hasMember;
+ import std.typecons : Ternary;
+
+ static assert(minAlign.isGoodStaticAlignment);
+ static assert(size >= minAlign);
+
+ version (X86) enum growDownwards = Yes.growDownwards;
+ else version (X86_64) enum growDownwards = Yes.growDownwards;
+ else version (ARM) enum growDownwards = Yes.growDownwards;
+ else version (AArch64) enum growDownwards = Yes.growDownwards;
+ else version (HPPA) enum growDownwards = No.growDownwards;
+ else version (PPC) enum growDownwards = Yes.growDownwards;
+ else version (PPC64) enum growDownwards = Yes.growDownwards;
+ else version (RISCV32) enum growDownwards = Yes.growDownwards;
+ else version (RISCV64) enum growDownwards = Yes.growDownwards;
+ else version (MIPS32) enum growDownwards = Yes.growDownwards;
+ else version (MIPS64) enum growDownwards = Yes.growDownwards;
+ else version (SPARC) enum growDownwards = Yes.growDownwards;
+ else version (SPARC64) enum growDownwards = Yes.growDownwards;
+ else version (SystemZ) enum growDownwards = Yes.growDownwards;
+ else static assert(0, "Dunno how the stack grows on this architecture.");
+
+ @disable this(this);
+
+ // state {
+ private BorrowedRegion!(minAlign, growDownwards) _impl;
+ union
+ {
+ private ubyte[size] _store = void;
+ private double _forAlignmentOnly1;
+ }
+ // }
+
+ /**
+ An alias for `minAlign`, which must be a valid alignment (nonzero power
+ of 2). The start of the region and all allocation requests will be rounded
+ up to a multiple of the alignment.
+
+ ----
+ InSituRegion!(4096) a1;
+ assert(a1.alignment == platformAlignment);
+ InSituRegion!(4096, 64) a2;
+ assert(a2.alignment == 64);
+ ----
+ */
+ alias alignment = minAlign;
+
+ private void lazyInit()
+ {
+ assert(!_impl._current);
+ _impl = typeof(_impl)(_store);
+ assert(_impl._current.alignedAt(alignment));
+ }
+
+ /**
+ Allocates `bytes` and returns them, or `null` if the region cannot
+ accommodate the request. For efficiency reasons, if $(D bytes == 0) the
+ function returns an empty non-null slice.
+ */
+ void[] allocate(size_t n)
+ {
+ // Fast path
+ entry:
+ auto result = _impl.allocate(n);
+ if (result.length == n) return result;
+ // Slow path
+ if (_impl._current) return null; // no more room
+ lazyInit;
+ assert(_impl._current);
+ goto entry;
+ }
+
+ /**
+ As above, but the memory allocated is aligned at `a` bytes.
+ */
+ void[] alignedAllocate(size_t n, uint a)
+ {
+ // Fast path
+ entry:
+ auto result = _impl.alignedAllocate(n, a);
+ if (result.length == n) return result;
+ // Slow path
if (_impl._current) return null; // no more room
lazyInit;
assert(_impl._current);
@@ -992,7 +1175,7 @@ version (Posix) @system nothrow @nogc unittest
The threadsafe version of the `Region` allocator.
Allocations and deallocations are lock-free based using $(REF cas, core,atomic).
*/
-shared struct SharedRegion(ParentAllocator = NullAllocator,
+shared struct SharedRegion(ParentAllocator,
uint minAlign = platformAlignment,
Flag!"growDownwards" growDownwards = No.growDownwards)
{
@@ -1016,45 +1199,36 @@ shared struct SharedRegion(ParentAllocator = NullAllocator,
{
alias parent = ParentAllocator.instance;
}
- private shared void* _current, _begin, _end;
+ private shared SharedBorrowedRegion!(minAlign, growDownwards) _impl;
private void* roundedBegin() const pure nothrow @trusted @nogc
{
- return cast(void*) roundUpToAlignment(cast(size_t) _begin, alignment);
+ return _impl.roundedBegin;
}
private void* roundedEnd() const pure nothrow @trusted @nogc
{
- return cast(void*) roundDownToAlignment(cast(size_t) _end, alignment);
+ return _impl.roundedEnd;
}
/**
Constructs a region backed by a user-provided store.
- Assumes the memory was allocated with `ParentAllocator`
- (if different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator)).
+ Assumes the memory was allocated with `ParentAllocator`.
Params:
- store = User-provided store backing up the region. If `ParentAllocator`
- is different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator), memory is assumed to
- have been allocated with `ParentAllocator`.
- n = Bytes to allocate using `ParentAllocator`. This constructor is only
- defined If `ParentAllocator` is different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator). If
- `parent.allocate(n)` returns `null`, the region will be initialized
- as empty (correctly initialized but unable to allocate).
+ store = User-provided store backing up the region. Assumed to have been
+ allocated with `ParentAllocator`.
+ n = Bytes to allocate using `ParentAllocator`. If `parent.allocate(n)`
+ returns `null`, the region will be initialized as empty (correctly
+ initialized but unable to allocate).
*/
this(ubyte[] store) pure nothrow @nogc
{
- _begin = cast(typeof(_begin)) store.ptr;
- _end = cast(typeof(_end)) (store.ptr + store.length);
- static if (growDownwards)
- _current = cast(typeof(_current)) roundedEnd();
- else
- _current = cast(typeof(_current)) roundedBegin();
+ _impl = store;
}
/// Ditto
- static if (!is(ParentAllocator == NullAllocator))
this(size_t n)
{
this(cast(ubyte[]) (parent.allocate(n.roundUpToAlignment(alignment))));
@@ -1065,7 +1239,7 @@ shared struct SharedRegion(ParentAllocator = NullAllocator,
*/
size_t goodAllocSize(size_t n) const pure nothrow @safe @nogc
{
- return n.roundUpToAlignment(alignment);
+ return _impl.goodAllocSize(n);
}
/**
@@ -1086,38 +1260,7 @@ shared struct SharedRegion(ParentAllocator = NullAllocator,
*/
void[] allocate(size_t n) pure nothrow @trusted @nogc
{
- import core.atomic : cas, atomicLoad;
-
- if (n == 0) return null;
- const rounded = goodAllocSize(n);
-
- shared void* localCurrent, localNewCurrent;
- static if (growDownwards)
- {
- do
- {
- localCurrent = atomicLoad(_current);
- localNewCurrent = localCurrent - rounded;
- if (localNewCurrent > localCurrent || localNewCurrent < _begin)
- return null;
- } while (!cas(&_current, localCurrent, localNewCurrent));
-
- return cast(void[]) localNewCurrent[0 .. n];
- }
- else
- {
- do
- {
- localCurrent = atomicLoad(_current);
- localNewCurrent = localCurrent + rounded;
- if (localNewCurrent < localCurrent || localNewCurrent > _end)
- return null;
- } while (!cas(&_current, localCurrent, localNewCurrent));
-
- return cast(void[]) localCurrent[0 .. n];
- }
-
- assert(0, "Unexpected error in SharedRegion.allocate");
+ return _impl.allocate(n);
}
/**
@@ -1131,97 +1274,30 @@ shared struct SharedRegion(ParentAllocator = NullAllocator,
*/
bool deallocate(void[] b) pure nothrow @nogc
{
- import core.atomic : cas, atomicLoad;
-
- const rounded = goodAllocSize(b.length);
- shared void* localCurrent, localNewCurrent;
-
- // The cas is done only once, because only the last allocation can be reverted
- localCurrent = atomicLoad(_current);
- static if (growDownwards)
- {
- localNewCurrent = localCurrent + rounded;
- if (b.ptr == localCurrent)
- return cas(&_current, localCurrent, localNewCurrent);
- }
- else
- {
- localNewCurrent = localCurrent - rounded;
- if (b.ptr == localNewCurrent)
- return cas(&_current, localCurrent, localNewCurrent);
- }
-
- return false;
- }
-
- /**
- Deallocates all memory allocated by this region, which can be subsequently
- reused for new allocations.
- */
- bool deallocateAll() pure nothrow @nogc
- {
- import core.atomic : atomicStore;
- static if (growDownwards)
- {
- atomicStore(_current, cast(shared(void*)) roundedEnd());
- }
- else
- {
- atomicStore(_current, cast(shared(void*)) roundedBegin());
- }
- return true;
+ return _impl.deallocate(b);
}
- /**
- Allocates `n` bytes of memory aligned at alignment `a`.
- Params:
- n = number of bytes to allocate
- a = alignment for the allocated block
-
- Returns:
- Either a suitable block of `n` bytes aligned at `a`, or `null`.
- */
- void[] alignedAllocate(size_t n, uint a) pure nothrow @trusted @nogc
- {
- import core.atomic : cas, atomicLoad;
- import std.math.traits : isPowerOf2;
-
- assert(a.isPowerOf2);
- if (n == 0) return null;
-
- const rounded = goodAllocSize(n);
- shared void* localCurrent, localNewCurrent;
-
- static if (growDownwards)
- {
- do
- {
- localCurrent = atomicLoad(_current);
- auto alignedCurrent = cast(void*)(localCurrent - rounded);
- localNewCurrent = cast(shared(void*)) alignedCurrent.alignDownTo(a);
- if (alignedCurrent > localCurrent || localNewCurrent > alignedCurrent ||
- localNewCurrent < _begin)
- return null;
- } while (!cas(&_current, localCurrent, localNewCurrent));
-
- return cast(void[]) localNewCurrent[0 .. n];
- }
- else
- {
- do
- {
- localCurrent = atomicLoad(_current);
- auto alignedCurrent = alignUpTo(cast(void*) localCurrent, a);
- localNewCurrent = cast(shared(void*)) (alignedCurrent + rounded);
- if (alignedCurrent < localCurrent || localNewCurrent < alignedCurrent ||
- localNewCurrent > _end)
- return null;
- } while (!cas(&_current, localCurrent, localNewCurrent));
-
- return cast(void[]) (localNewCurrent - rounded)[0 .. n];
- }
+ /**
+ Deallocates all memory allocated by this region, which can be subsequently
+ reused for new allocations.
+ */
+ bool deallocateAll() pure nothrow @nogc
+ {
+ return _impl.deallocateAll;
+ }
+
+ /**
+ Allocates `n` bytes of memory aligned at alignment `a`.
+ Params:
+ n = number of bytes to allocate
+ a = alignment for the allocated block
- assert(0, "Unexpected error in SharedRegion.alignedAllocate");
+ Returns:
+ Either a suitable block of `n` bytes aligned at `a`, or `null`.
+ */
+ void[] alignedAllocate(size_t n, uint a) pure nothrow @trusted @nogc
+ {
+ return _impl.alignedAllocate(n, a);
}
/**
@@ -1236,7 +1312,7 @@ shared struct SharedRegion(ParentAllocator = NullAllocator,
*/
Ternary owns(const void[] b) const pure nothrow @trusted @nogc
{
- return Ternary(b && (&b[0] >= _begin) && (&b[0] + b.length <= _end));
+ return _impl.owns(b);
}
/**
@@ -1245,25 +1321,17 @@ shared struct SharedRegion(ParentAllocator = NullAllocator,
*/
Ternary empty() const pure nothrow @safe @nogc
{
- import core.atomic : atomicLoad;
-
- auto localCurrent = atomicLoad(_current);
- static if (growDownwards)
- return Ternary(localCurrent == roundedEnd());
- else
- return Ternary(localCurrent == roundedBegin());
+ return _impl.empty;
}
/**
- If `ParentAllocator` is not $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator) and defines `deallocate`,
- the region defines a destructor that uses `ParentAllocator.deallocate` to free the
- memory chunk.
+ If `ParentAllocator` defines `deallocate`, the region defines a destructor
+ that uses `ParentAllocator.deallocate` to free the memory chunk.
*/
- static if (!is(ParentAllocator == NullAllocator)
- && hasMember!(ParentAllocator, "deallocate"))
+ static if (hasMember!(ParentAllocator, "deallocate"))
~this()
{
- parent.deallocate(cast(void[]) _begin[0 .. _end - _begin]);
+ with (_impl) parent.deallocate(cast(void[]) _begin[0 .. _end - _begin]);
}
}
@@ -1397,3 +1465,250 @@ shared struct SharedRegion(ParentAllocator = NullAllocator,
testAlloc(a1, true);
testAlloc(a2, false);
}
+
+/**
+A `SharedBorrowedRegion` allocates directly from a user-provided block of memory.
+
+Unlike a `SharedRegion`, a `SharedBorrowedRegion` does not own the memory it
+allocates from and will not deallocate that memory upon destruction. Instead,
+it is the user's responsibility to ensure that the memory is properly disposed
+of.
+
+In all other respects, a `SharedBorrowedRegion` behaves exactly like a `SharedRegion`.
+*/
+shared struct SharedBorrowedRegion(uint minAlign = platformAlignment,
+ Flag!"growDownwards" growDownwards = No.growDownwards)
+{
+ static assert(minAlign.isGoodStaticAlignment);
+
+ import std.typecons : Ternary;
+
+ // state
+ private void* _current, _begin, _end;
+
+ private void* roundedBegin() shared const pure nothrow @trusted @nogc
+ {
+ return cast(void*) roundUpToAlignment(cast(size_t) _begin, alignment);
+ }
+
+ private void* roundedEnd() shared const pure nothrow @trusted @nogc
+ {
+ return cast(void*) roundDownToAlignment(cast(size_t) _end, alignment);
+ }
+
+ /**
+ Constructs a region backed by a user-provided store.
+
+ Params:
+ store = User-provided store backing up the region. Must not be aliased.
+ */
+ this(ubyte[] store) shared pure nothrow @nogc
+ {
+ _begin = cast(typeof(_begin)) store.ptr;
+ _end = cast(typeof(_end)) (store.ptr + store.length);
+ static if (growDownwards)
+ _current = cast(typeof(_current)) roundedEnd();
+ else
+ _current = cast(typeof(_current)) roundedBegin();
+ }
+
+ /*
+ TODO: The postblit of `SharedBorrowedRegion` should be disabled because
+ such objects should not be copied around naively.
+ */
+
+ /**
+ Rounds the given size to a multiple of the `alignment`
+ */
+ size_t goodAllocSize(size_t n) shared const pure nothrow @safe @nogc
+ {
+ return n.roundUpToAlignment(alignment);
+ }
+
+ /**
+ Alignment offered.
+ */
+ alias alignment = minAlign;
+
+ /**
+ Allocates `n` bytes of memory. The allocation is served by atomically incrementing
+ a pointer which keeps track of the current used space.
+
+ Params:
+ n = number of bytes to allocate
+
+ Returns:
+ A properly-aligned buffer of size `n`, or `null` if request could not
+ be satisfied.
+ */
+ void[] allocate(size_t n) shared pure nothrow @trusted @nogc
+ {
+ import core.atomic : cas, atomicLoad;
+
+ if (n == 0) return null;
+ const rounded = goodAllocSize(n);
+
+ shared void* localCurrent, localNewCurrent;
+ static if (growDownwards)
+ {
+ do
+ {
+ localCurrent = atomicLoad(_current);
+ localNewCurrent = localCurrent - rounded;
+ if (localNewCurrent > localCurrent || localNewCurrent < _begin)
+ return null;
+ } while (!cas(&_current, localCurrent, localNewCurrent));
+
+ return cast(void[]) localNewCurrent[0 .. n];
+ }
+ else
+ {
+ do
+ {
+ localCurrent = atomicLoad(_current);
+ localNewCurrent = localCurrent + rounded;
+ if (localNewCurrent < localCurrent || localNewCurrent > _end)
+ return null;
+ } while (!cas(&_current, localCurrent, localNewCurrent));
+
+ return cast(void[]) localCurrent[0 .. n];
+ }
+
+ assert(0, "Unexpected error in SharedBorrowedRegion.allocate");
+ }
+
+ /**
+ Allocates `n` bytes of memory aligned at alignment `a`.
+
+ Params:
+ n = number of bytes to allocate
+ a = alignment for the allocated block
+
+ Returns:
+ Either a suitable block of `n` bytes aligned at `a`, or `null`.
+ */
+ void[] alignedAllocate(size_t n, uint a) shared pure nothrow @trusted @nogc
+ {
+ import core.atomic : cas, atomicLoad;
+ import std.math.traits : isPowerOf2;
+
+ assert(a.isPowerOf2);
+ if (n == 0) return null;
+
+ const rounded = goodAllocSize(n);
+ shared void* localCurrent, localNewCurrent;
+
+ static if (growDownwards)
+ {
+ do
+ {
+ localCurrent = atomicLoad(_current);
+ auto alignedCurrent = cast(void*)(localCurrent - rounded);
+ localNewCurrent = cast(shared(void*)) alignedCurrent.alignDownTo(a);
+ if (alignedCurrent > localCurrent || localNewCurrent > alignedCurrent ||
+ localNewCurrent < _begin)
+ return null;
+ } while (!cas(&_current, localCurrent, localNewCurrent));
+
+ return cast(void[]) localNewCurrent[0 .. n];
+ }
+ else
+ {
+ do
+ {
+ localCurrent = atomicLoad(_current);
+ auto alignedCurrent = alignUpTo(cast(void*) localCurrent, a);
+ localNewCurrent = cast(shared(void*)) (alignedCurrent + rounded);
+ if (alignedCurrent < localCurrent || localNewCurrent < alignedCurrent ||
+ localNewCurrent > _end)
+ return null;
+ } while (!cas(&_current, localCurrent, localNewCurrent));
+
+ return cast(void[]) (localNewCurrent - rounded)[0 .. n];
+ }
+
+ assert(0, "Unexpected error in SharedBorrowedRegion.alignedAllocate");
+ }
+
+ /**
+ Deallocates `b`. This works only if `b` was obtained as the last call
+ to `allocate`; otherwise (i.e. another allocation has occurred since) it
+ does nothing.
+
+ Params:
+ b = Block previously obtained by a call to `allocate` against this
+ allocator (`null` is allowed).
+ */
+ bool deallocate(void[] b) shared pure nothrow @nogc
+ {
+ import core.atomic : cas, atomicLoad;
+
+ const rounded = goodAllocSize(b.length);
+ shared void* localCurrent, localNewCurrent;
+
+ // The cas is done only once, because only the last allocation can be reverted
+ localCurrent = atomicLoad(_current);
+ static if (growDownwards)
+ {
+ localNewCurrent = localCurrent + rounded;
+ if (b.ptr == localCurrent)
+ return cas(&_current, localCurrent, localNewCurrent);
+ }
+ else
+ {
+ localNewCurrent = localCurrent - rounded;
+ if (b.ptr == localNewCurrent)
+ return cas(&_current, localCurrent, localNewCurrent);
+ }
+
+ return false;
+ }
+
+ /**
+ Deallocates all memory allocated by this region, which can be subsequently
+ reused for new allocations.
+ */
+ bool deallocateAll() shared pure nothrow @nogc
+ {
+ import core.atomic : atomicStore;
+ static if (growDownwards)
+ {
+ atomicStore(_current, cast(shared(void*)) roundedEnd());
+ }
+ else
+ {
+ atomicStore(_current, cast(shared(void*)) roundedBegin());
+ }
+ return true;
+ }
+
+ /**
+ Queries whether `b` has been allocated with this region.
+
+ Params:
+ b = Arbitrary block of memory (`null` is allowed; `owns(null)` returns
+ `false`).
+
+ Returns:
+ `true` if `b` has been allocated with this region, `false` otherwise.
+ */
+ Ternary owns(const void[] b) shared const pure nothrow @trusted @nogc
+ {
+ return Ternary(b && (&b[0] >= _begin) && (&b[0] + b.length <= _end));
+ }
+
+ /**
+ Returns `Ternary.yes` if no memory has been allocated in this region,
+ `Ternary.no` otherwise. (Never returns `Ternary.unknown`.)
+ */
+ Ternary empty() shared const pure nothrow @safe @nogc
+ {
+ import core.atomic : atomicLoad;
+
+ auto localCurrent = atomicLoad(_current);
+ static if (growDownwards)
+ return Ternary(localCurrent == roundedEnd());
+ else
+ return Ternary(localCurrent == roundedBegin());
+ }
+}
diff --git a/std/experimental/allocator/building_blocks/scoped_allocator.d b/std/experimental/allocator/building_blocks/scoped_allocator.d
index 8fc7584f796..96859b0762f 100644
--- a/std/experimental/allocator/building_blocks/scoped_allocator.d
+++ b/std/experimental/allocator/building_blocks/scoped_allocator.d
@@ -259,10 +259,10 @@ version (StdUnittest)
// Test that deallocateAll infers from parent
@system unittest
{
- import std.experimental.allocator.building_blocks.region : Region;
+ import std.experimental.allocator.building_blocks.region : BorrowedRegion;
- ScopedAllocator!(Region!()) a;
- a.parent.parent = Region!()(new ubyte[1024 * 64]);
+ ScopedAllocator!(BorrowedRegion!()) a;
+ a.parent.parent = BorrowedRegion!()(new ubyte[1024 * 64]);
auto b = a.allocate(42);
assert(b.length == 42);
assert((() pure nothrow @safe @nogc => a.expand(b, 22))());
diff --git a/std/experimental/allocator/building_blocks/segregator.d b/std/experimental/allocator/building_blocks/segregator.d
index 655db4567ce..ff089bddc13 100644
--- a/std/experimental/allocator/building_blocks/segregator.d
+++ b/std/experimental/allocator/building_blocks/segregator.d
@@ -503,12 +503,12 @@ if (Args.length > 3)
@system unittest
{
- import std.experimental.allocator.building_blocks.region : Region;
+ import std.experimental.allocator.building_blocks.region : BorrowedRegion;
import std.typecons : Ternary;
- auto a = Segregator!(10_240, Region!(), Region!())(
- Region!()(new ubyte[4096 * 1024]),
- Region!()(new ubyte[4096 * 1024]));
+ auto a = Segregator!(10_240, BorrowedRegion!(), BorrowedRegion!())(
+ BorrowedRegion!()(new ubyte[4096 * 1024]),
+ BorrowedRegion!()(new ubyte[4096 * 1024]));
assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes);
auto b = a.alignedAllocate(42, 8);
diff --git a/std/experimental/allocator/building_blocks/stats_collector.d b/std/experimental/allocator/building_blocks/stats_collector.d
index d57b3edd16f..3770af10ceb 100644
--- a/std/experimental/allocator/building_blocks/stats_collector.d
+++ b/std/experimental/allocator/building_blocks/stats_collector.d
@@ -845,9 +845,9 @@ public:
@system unittest
{
- import std.experimental.allocator.building_blocks.region : Region;
+ import std.experimental.allocator.building_blocks.region : BorrowedRegion;
- auto a = StatsCollector!(Region!(), Options.all, Options.all)(Region!()(new ubyte[1024 * 64]));
+ auto a = StatsCollector!(BorrowedRegion!(), Options.all, Options.all)(BorrowedRegion!()(new ubyte[1024 * 64]));
auto b = a.allocate(42);
assert(b.length == 42);
// Test that reallocate infers from parent
@@ -859,9 +859,9 @@ public:
@system unittest
{
- import std.experimental.allocator.building_blocks.region : Region;
+ import std.experimental.allocator.building_blocks.region : BorrowedRegion;
- auto a = StatsCollector!(Region!(), Options.all)(Region!()(new ubyte[1024 * 64]));
+ auto a = StatsCollector!(BorrowedRegion!(), Options.all)(BorrowedRegion!()(new ubyte[1024 * 64]));
auto b = a.alignedAllocate(42, 128);
assert(b.length == 42);
assert(b.ptr.alignedAt(128));
diff --git a/std/experimental/allocator/package.d b/std/experimental/allocator/package.d
index 62f848f11fc..217792676b5 100644
--- a/std/experimental/allocator/package.d
+++ b/std/experimental/allocator/package.d
@@ -547,24 +547,24 @@ nothrow:
@system unittest
{
- import std.experimental.allocator.building_blocks.region : Region;
+ import std.experimental.allocator.building_blocks.region : BorrowedRegion;
import std.conv : emplace;
- auto reg = Region!()(new ubyte[1024]);
- auto state = reg.allocate(stateSize!(CAllocatorImpl!(Region!(), Yes.indirect)));
- auto regObj = emplace!(CAllocatorImpl!(Region!(), Yes.indirect))(state, ®);
+ auto reg = BorrowedRegion!()(new ubyte[1024]);
+ auto state = reg.allocate(stateSize!(CAllocatorImpl!(BorrowedRegion!(), Yes.indirect)));
+ auto regObj = emplace!(CAllocatorImpl!(BorrowedRegion!(), Yes.indirect))(state, ®);
auto rcalloc = RCIAllocator(regObj);
auto b = rcalloc.allocate(10);
assert(b.length == 10);
// The reference counting is zero based
- assert((cast(CAllocatorImpl!(Region!(), Yes.indirect))(rcalloc._alloc)).rc == 1);
+ assert((cast(CAllocatorImpl!(BorrowedRegion!(), Yes.indirect))(rcalloc._alloc)).rc == 1);
{
auto rca2 = rcalloc;
- assert((cast(CAllocatorImpl!(Region!(), Yes.indirect))(rcalloc._alloc)).rc == 2);
+ assert((cast(CAllocatorImpl!(BorrowedRegion!(), Yes.indirect))(rcalloc._alloc)).rc == 2);
}
- assert((cast(CAllocatorImpl!(Region!(), Yes.indirect))(rcalloc._alloc)).rc == 1);
+ assert((cast(CAllocatorImpl!(BorrowedRegion!(), Yes.indirect))(rcalloc._alloc)).rc == 1);
}
@system unittest
diff --git a/std/experimental/typecons.d b/std/experimental/typecons.d
deleted file mode 100644
index 46e21e77e7a..00000000000
--- a/std/experimental/typecons.d
+++ /dev/null
@@ -1,1083 +0,0 @@
-// Written in the D programming language.
-
-/**
-This module implements experimental additions/modifications to $(MREF std, typecons).
-
-Use this module to test out new functionality for $(REF wrap, std, typecons)
-which allows for a struct to be wrapped against an interface; the
-implementation in $(MREF std, typecons) only allows for classes to use the wrap
-functionality.
-
-Source: $(PHOBOSSRC std/experimental/typecons.d)
-
-Copyright: Copyright the respective authors, 2008-
-License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
-Authors: $(HTTP erdani.org, Andrei Alexandrescu),
- $(HTTP bartoszmilewski.wordpress.com, Bartosz Milewski),
- Don Clugston,
- Shin Fujishiro,
- Kenji Hara
- */
-module std.experimental.typecons;
-
-import std.meta; // : AliasSeq, allSatisfy;
-import std.traits;
-
-import std.typecons : Tuple, tuple, Bind, DerivedFunctionType, GetOverloadedMethods;
-
-private
-{
- pragma(mangle, "_d_toObject")
- extern(C) pure nothrow Object typecons_d_toObject(void* p);
-}
-
-/*
- * Avoids opCast operator overloading.
- */
-private template dynamicCast(T)
-if (is(T == class) || is(T == interface))
-{
- @trusted
- T dynamicCast(S)(inout S source)
- if (is(S == class) || is(S == interface))
- {
- static if (is(Unqual!S : Unqual!T))
- {
- import std.traits : QualifierOf;
- alias Qual = QualifierOf!S; // SharedOf or MutableOf
- alias TmpT = Qual!(Unqual!T);
- inout(TmpT) tmp = source; // bypass opCast by implicit conversion
- return *cast(T*)(&tmp); // + variable pointer cast + dereference
- }
- else
- {
- return cast(T) typecons_d_toObject(*cast(void**)(&source));
- }
- }
-}
-
-@system unittest
-{
- class C { @disable void opCast(T)(); }
- auto c = new C;
- static assert(!__traits(compiles, cast(Object) c));
- auto o = dynamicCast!Object(c);
- assert(c is o);
-
- interface I { @disable void opCast(T)(); Object instance(); }
- interface J { @disable void opCast(T)(); Object instance(); }
- class D : I, J { Object instance() { return this; } }
- I i = new D();
- static assert(!__traits(compiles, cast(J) i));
- J j = dynamicCast!J(i);
- assert(i.instance() is j.instance());
-}
-
-/*
- * Determines if the `Source` type satisfies all interface requirements of
- * `Targets`.
- */
-private template implementsInterface(Source, Targets...)
-if (Targets.length >= 1 && allSatisfy!(isMutable, Targets))
-{
- import std.meta : staticMap;
-
- // strict upcast
- bool implementsInterface()()
- if (Targets.length == 1 && is(Source : Targets[0]))
- {
- return true;
- }
- // structural upcast
- template implementsInterface()
- if (!allSatisfy!(Bind!(isImplicitlyConvertible, Source), Targets))
- {
- auto implementsInterface()
- {
- return hasRequiredMethods!();
- }
-
- // list of FuncInfo
- alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!Targets);
- // list of function symbols
- alias SourceMembers = GetOverloadedMethods!Source;
-
- // Check whether all of SourceMembers satisfy covariance target in
- // TargetMembers
- template hasRequiredMethods(size_t i = 0)
- {
- static if (i >= TargetMembers.length)
- enum hasRequiredMethods = true;
- else
- {
- enum foundFunc = findCovariantFunction!(TargetMembers[i], Source, SourceMembers);
-
- version (StdUnittest) {}
- else debug
- {
- static if (foundFunc == -1)
- pragma(msg, "Could not locate matching function for: ",
- TargetMembers[i].stringof);
- }
-
- enum hasRequiredMethods =
- foundFunc != -1 &&
- hasRequiredMethods!(i + 1);
- }
- }
- }
-}
-// ditto
-private template implementsInterface(Source, Targets...)
-if (Targets.length >= 1 && !allSatisfy!(isMutable, Targets))
-{
- import std.meta : staticMap;
-
- alias implementsInterface = .implementsInterface!(Source, staticMap!(Unqual, Targets));
-}
-
-@safe unittest
-{
- interface Foo {
- void foo();
- }
- interface Bar {
- void bar();
- }
- interface FooBar : Foo, Bar {
- void foobar();
- }
-
- struct A {
- void foo() {}
- }
- struct B {
- void bar() {}
- void foobar() {}
- }
- class C {
- void foo() {}
- void bar() {}
- }
- struct D {
- void foo() {}
- void bar() {}
- void foobar() {}
- }
- // Implements interface
- static assert(implementsInterface!(A, Foo));
- static assert(implementsInterface!(A, const(Foo)));
- static assert(implementsInterface!(A, immutable(Foo)));
- // Doesn't implement interface
- static assert(!implementsInterface!(B, Foo));
- static assert(implementsInterface!(B, Bar));
- // Implements both interfaces
- static assert(implementsInterface!(C, Foo));
- static assert(implementsInterface!(C, Bar));
- static assert(implementsInterface!(C, Foo, Bar));
- static assert(implementsInterface!(C, Foo, const(Bar)));
- static assert(!implementsInterface!(A, Foo, Bar));
- static assert(!implementsInterface!(A, Foo, immutable(Bar)));
- // Implements inherited
- static assert(implementsInterface!(D, FooBar));
- static assert(!implementsInterface!(B, FooBar));
-}
-
-private enum isInterface(ConceptType) = is(ConceptType == interface);
-
-///
-template wrap(Targets...)
-if (Targets.length >= 1 && allSatisfy!(isInterface, Targets))
-{
- import std.meta : ApplyLeft, staticMap;
-
- version (StdDdoc)
- {
- /**
- * Wrap src in an anonymous class implementing $(D_PARAM Targets).
- *
- * wrap creates an internal wrapper class which implements the
- * interfaces in `Targets` using the methods of `src`, then returns a
- * GC-allocated instance of it.
- *
- * $(D_PARAM Source) can be either a `class` or a `struct`, but it must
- * $(I structurally conform) with all the $(D_PARAM Targets)
- * interfaces; i.e. it must provide concrete methods with compatible
- * signatures of those in $(D_PARAM Targets).
- *
- * If $(D_PARAM Source) is a `struct` then wrapping/unwrapping will
- * create a copy; it is not possible to affect the original `struct`
- * through the wrapper.
- *
- * The returned object additionally supports $(LREF unwrap).
- *
- * Note:
- * If $(D_PARAM Targets) has only one entry and $(D_PARAM Source) is a
- * class which explicitly implements it, wrap simply returns src
- * upcasted to `Targets[0]`.
- *
- * Bugs:
- * wrap does not support interfaces which take their own type as either
- * a parameter type or return type in any of its methods.
- *
- * See_Also: $(LREF unwrap) for examples
- */
- auto wrap(Source)(inout Source src)
- if (implementsInterface!(Source, Targets));
- }
-
- static if (!allSatisfy!(isMutable, Targets))
- alias wrap = .wrap!(staticMap!(Unqual, Targets));
- else
- {
- // strict upcast
- auto wrap(Source)(inout Source src)
- if (Targets.length == 1 && is(Source : Targets[0]))
- {
- alias T = Select!(is(Source == shared), shared Targets[0], Targets[0]);
- return dynamicCast!(inout T)(src);
- }
-
- // structural upcast
- template wrap(Source)
- if (!allSatisfy!(ApplyLeft!(isImplicitlyConvertible, Source), Targets))
- {
- auto wrap(inout Source src)
- {
- static assert(implementsInterface!(Source, Targets),
- "Source "~Source.stringof~
- " does not have structural conformance to "~
- Targets.stringof);
-
- alias T = Select!(is(Source == shared), shared Impl, Impl);
- return new inout T(src);
- }
-
- // list of FuncInfo
- alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!(Targets));
- // list of function symbols
- alias SourceMembers = GetOverloadedMethods!Source;
-
- static if (is(Source == class) || is(Source == interface))
- alias StructuralType = Object;
- else static if (is(Source == struct))
- alias StructuralType = Source;
-
- // Check whether all of SourceMembers satisfy covariance target in TargetMembers
- // Internal wrapper class
- final class Impl : Structural!StructuralType, Targets
- {
- private:
- Source _wrap_source;
-
- this( inout Source s) inout @safe pure nothrow { _wrap_source = s; }
- this(shared inout Source s) shared inout @safe pure nothrow { _wrap_source = s; }
-
- static if (is(Source == class) || is(Source == interface))
- {
- // BUG: making private should work with NVI.
- protected inout(Object) _wrap_getSource() inout @safe
- {
- return dynamicCast!(inout Object)(_wrap_source);
- }
- }
- else
- {
- // BUG: making private should work with NVI.
- protected inout(Source) _wrap_getSource() inout @safe
- {
- return _wrap_source;
- }
- }
-
- import std.conv : to;
- import core.lifetime : forward;
- template generateFun(size_t i)
- {
- enum name = TargetMembers[i].name;
- enum fa = functionAttributes!(TargetMembers[i].type);
- static args(int num)()
- {
- string r;
- bool first = true;
- foreach (i; 0 .. num)
- {
- import std.conv : to;
- r ~= (first ? "" : ", ") ~ " a" ~ (i+1).to!string;
- first = false;
- }
- return r;
- }
- static if (fa & FunctionAttribute.property)
- {
- static if (Parameters!(TargetMembers[i].type).length == 0)
- enum fbody = "_wrap_source."~name;
- else
- enum fbody = "_wrap_source."~name~" = a1";
- }
- else
- {
- enum fbody = "_wrap_source."~name~"("~args!(Parameters!(TargetMembers[i].type).length)~")";
- }
- enum generateFun =
- "override "~wrapperSignature!(TargetMembers[i]) ~
- "{ return "~fbody~"; }";
- }
-
- public:
- static foreach (i; 0 .. TargetMembers.length)
- mixin(generateFun!i);
- }
- }
- }
-}
-
-// Build a signature that matches the provided function
-// Each argument will be provided a name in the form a#
-private template wrapperSignature(alias fun)
-{
- enum name = fun.name;
- enum fa = functionAttributes!(fun.type);
- static @property stc()
- {
- string r;
- if (fa & FunctionAttribute.property) r ~= "@property ";
- if (fa & FunctionAttribute.ref_) r ~= "ref ";
- if (fa & FunctionAttribute.pure_) r ~= "pure ";
- if (fa & FunctionAttribute.nothrow_) r ~= "nothrow ";
- if (fa & FunctionAttribute.trusted) r ~= "@trusted ";
- if (fa & FunctionAttribute.safe) r ~= "@safe ";
- return r;
- }
- static @property mod()
- {
- alias type = AliasSeq!(fun.type)[0];
- string r;
- static if (is(type == immutable)) r ~= " immutable";
- else
- {
- static if (is(type == shared)) r ~= " shared";
- static if (is(type == const)) r ~= " const";
- else static if (is(type == inout)) r ~= " inout";
- //else --> mutable
- }
- return r;
- }
- alias param = Parameters!(fun.type);
- static @property wrapperParameters()
- {
- string r;
- bool first = true;
- foreach (i, p; param)
- {
- import std.conv : to;
- r ~= (first ? "" : ", ") ~ p.stringof ~ " a" ~ (i+1).to!string;
- first = false;
- }
- return r;
- }
-
- enum wrapperSignature =
- stc~ReturnType!(fun.type).stringof ~ " "
- ~ name~"("~wrapperParameters~")"~mod;
-}
-
-@safe unittest
-{
- interface M
- {
- void f1();
- void f2(string[] args, int count);
- void f3(string[] args, int count) pure const;
- }
-
- alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!M);
- static assert(wrapperSignature!(TargetMembers[0]) == "void f1()"
- , wrapperSignature!(TargetMembers[0]));
-
- static assert(wrapperSignature!(TargetMembers[1]) == "void f2(string[] a1, int a2)"
- , wrapperSignature!(TargetMembers[1]));
-
- static assert(wrapperSignature!(TargetMembers[2]) == "pure void f3(string[] a1, int a2) const"
- , wrapperSignature!(TargetMembers[2]));
-}
-
-// Internal class to support dynamic cross-casting
-private interface Structural(T)
-{
- inout(T) _wrap_getSource() inout @safe pure nothrow;
-}
-
-private string unwrapExceptionText(Source, Target)()
-{
- return Target.stringof~ " not wrapped into "~ Source.stringof;
-}
-
-version (StdDdoc)
-{
- /**
- * Extract object previously wrapped by $(LREF wrap).
- *
- * Params:
- * Target = type of wrapped object
- * src = wrapper object returned by $(LREF wrap)
- *
- * Returns: the wrapped object, or null if src is not a wrapper created
- * by $(LREF wrap) and $(D_PARAM Target) is a class
- *
- * Throws: $(REF ConvException, std, conv) when attempting to extract a
- * struct which is not the wrapped type
- *
- * See_also: $(LREF wrap)
- */
- public inout(Target) unwrap(Target, Source)(inout Source src);
-}
-
-///
-@system unittest
-{
- interface Quack
- {
- int quack();
- @property int height();
- }
- interface Flyer
- {
- @property int height();
- }
- class Duck : Quack
- {
- int quack() { return 1; }
- @property int height() { return 10; }
- }
- class Human
- {
- int quack() { return 2; }
- @property int height() { return 20; }
- }
- struct HumanStructure
- {
- int quack() { return 3; }
- @property int height() { return 30; }
- }
-
- Duck d1 = new Duck();
- Human h1 = new Human();
- HumanStructure hs1;
-
- interface Refreshable
- {
- int refresh();
- }
- // does not have structural conformance
- static assert(!__traits(compiles, d1.wrap!Refreshable));
- static assert(!__traits(compiles, h1.wrap!Refreshable));
- static assert(!__traits(compiles, hs1.wrap!Refreshable));
-
- // strict upcast
- Quack qd = d1.wrap!Quack;
- assert(qd is d1);
- assert(qd.quack() == 1); // calls Duck.quack
- // strict downcast
- Duck d2 = qd.unwrap!Duck;
- assert(d2 is d1);
-
- // structural upcast
- Quack qh = h1.wrap!Quack;
- Quack qhs = hs1.wrap!Quack;
- assert(qh.quack() == 2); // calls Human.quack
- assert(qhs.quack() == 3); // calls HumanStructure.quack
- // structural downcast
- Human h2 = qh.unwrap!Human;
- HumanStructure hs2 = qhs.unwrap!HumanStructure;
- assert(h2 is h1);
- assert(hs2 is hs1);
-
- // structural upcast (two steps)
- Quack qx = h1.wrap!Quack; // Human -> Quack
- Quack qxs = hs1.wrap!Quack; // HumanStructure -> Quack
- Flyer fx = qx.wrap!Flyer; // Quack -> Flyer
- Flyer fxs = qxs.wrap!Flyer; // Quack -> Flyer
- assert(fx.height == 20); // calls Human.height
- assert(fxs.height == 30); // calls HumanStructure.height
- // strucural downcast (two steps)
- Quack qy = fx.unwrap!Quack; // Flyer -> Quack
- Quack qys = fxs.unwrap!Quack; // Flyer -> Quack
- Human hy = qy.unwrap!Human; // Quack -> Human
- HumanStructure hys = qys.unwrap!HumanStructure; // Quack -> HumanStructure
- assert(hy is h1);
- assert(hys is hs1);
- // strucural downcast (one step)
- Human hz = fx.unwrap!Human; // Flyer -> Human
- HumanStructure hzs = fxs.unwrap!HumanStructure; // Flyer -> HumanStructure
- assert(hz is h1);
- assert(hzs is hs1);
-}
-
-///
-@system unittest
-{
- import std.traits : functionAttributes, FunctionAttribute;
- interface A { int run(); }
- interface B { int stop(); @property int status(); }
- class X
- {
- int run() { return 1; }
- int stop() { return 2; }
- @property int status() { return 3; }
- }
-
- auto x = new X();
- auto ab = x.wrap!(A, B);
- A a = ab;
- B b = ab;
- assert(a.run() == 1);
- assert(b.stop() == 2);
- assert(b.status == 3);
- static assert(functionAttributes!(typeof(ab).status) & FunctionAttribute.property);
-}
-
-template unwrap(Target)
-{
- static if (!isMutable!Target)
- alias unwrap = .unwrap!(Unqual!Target);
- else
- {
- // strict downcast
- auto unwrap(Source)(inout Source src)
- if (is(Target : Source))
- {
- alias T = Select!(is(Source == shared), shared Target, Target);
- return dynamicCast!(inout T)(src);
- }
-
- // structural downcast for struct target
- auto unwrap(Source)(inout Source src)
- if (is(Target == struct))
- {
- alias T = Select!(is(Source == shared), shared Target, Target);
- auto upCastSource = dynamicCast!Object(src); // remove qualifier
- do
- {
- if (auto a = dynamicCast!(Structural!Object)(upCastSource))
- {
- upCastSource = a._wrap_getSource();
- }
- else if (auto a = dynamicCast!(Structural!T)(upCastSource))
- {
- return a._wrap_getSource();
- }
- else
- {
- static if (hasMember!(Source, "_wrap_getSource"))
- return unwrap!Target(src._wrap_getSource());
- else
- break;
- }
- } while (upCastSource);
- import std.conv : ConvException;
- throw new ConvException(unwrapExceptionText!(Source,Target));
- }
- // structural downcast for class target
- auto unwrap(Source)(inout Source src)
- if (!is(Target : Source) && !is(Target == struct))
- {
- alias T = Select!(is(Source == shared), shared Target, Target);
- Object upCastSource = dynamicCast!(Object)(src); // remove qualifier
- do
- {
- // Unwrap classes
- if (auto a = dynamicCast!(Structural!Object)(upCastSource))
- {
- if (auto d = dynamicCast!(inout T)(upCastSource = a._wrap_getSource()))
- return d;
- }
- // Unwrap a structure of type T
- else if (auto a = dynamicCast!(Structural!T)(upCastSource))
- {
- return a._wrap_getSource();
- }
- // Unwrap class that already inherited from interface
- else if (auto d = dynamicCast!(inout T)(upCastSource))
- {
- return d;
- }
- // Recurse to find the struct Target within a wrapped tree
- else
- {
- static if (hasMember!(Source, "_wrap_getSource"))
- return unwrap!Target(src._wrap_getSource());
- else
- break;
- }
- } while (upCastSource);
- return null;
- }
- }
-}
-
-@system unittest
-{
- // Validate const/immutable
- class A
- {
- int draw() { return 1; }
- int draw(int v) { return v; }
-
- int draw() const { return 2; }
- int draw() shared { return 3; }
- int draw() shared const { return 4; }
- int draw() immutable { return 5; }
- }
- interface Drawable
- {
- int draw();
- int draw() const;
- int draw() shared;
- int draw() shared const;
- int draw() immutable;
- }
- interface Drawable2
- {
- int draw(int v);
- }
-
- auto ma = new A();
- auto sa = new shared A();
- auto ia = new immutable A();
- {
- Drawable md = ma.wrap!Drawable;
- const Drawable cd = ma.wrap!Drawable;
- shared Drawable sd = sa.wrap!Drawable;
- shared const Drawable scd = sa.wrap!Drawable;
- immutable Drawable id = ia.wrap!Drawable;
- assert( md.draw() == 1);
- assert( cd.draw() == 2);
- assert( sd.draw() == 3);
- assert(scd.draw() == 4);
- assert( id.draw() == 5);
- }
- {
- Drawable2 d = ma.wrap!Drawable2;
- static assert(!__traits(compiles, d.draw()));
- assert(d.draw(10) == 10);
- }
-}
-
-// https://issues.dlang.org/show_bug.cgi?id=10377
-@system unittest
-{
- import std.algorithm, std.range;
-
- interface MyInputRange(T)
- {
- @property T front();
- void popFront();
- @property bool empty();
- }
-
- //auto o = iota(0,10,1).inputRangeObject();
- //pragma(msg, __traits(allMembers, typeof(o)));
- auto r = iota(0,10,1).inputRangeObject().wrap!(MyInputRange!int)();
- assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]));
-}
-
-// https://issues.dlang.org/show_bug.cgi?id=10536
-@system unittest
-{
- interface Interface
- {
- int foo();
- }
- class Pluggable
- {
- int foo() { return 1; }
- @disable void opCast(T, this X)(); // !
- }
-
- Interface i = new Pluggable().wrap!Interface;
- assert(i.foo() == 1);
-}
-
-// https://issues.dlang.org/show_bug.cgi?id=10538
-@system unittest
-{
- interface Interface
- {
- int foo();
- int bar(int);
- }
- class Pluggable
- {
- int opDispatch(string name, A...)(A args) { return 100; }
- }
-
- Interface i = wrap!Interface(new Pluggable());
- assert(i.foo() == 100);
- assert(i.bar(10) == 100);
-}
-
-// Concat all Targets function members into one tuple
-private template ConcatInterfaceMembers(Targets...)
-{
- static if (Targets.length == 0)
- alias ConcatInterfaceMembers = AliasSeq!();
- else static if (Targets.length == 1)
- alias ConcatInterfaceMembers
- = AliasSeq!(GetOverloadedMethods!(Targets[0]));
- else
- alias ConcatInterfaceMembers = AliasSeq!(
- GetOverloadedMethods!(Targets[0]),
- ConcatInterfaceMembers!(Targets[1..$]));
-}
-// Remove duplicated functions based on the identifier name and function type covariance
-private template UniqMembers(members...)
-{
- template FuncInfo(string s, F)
- {
- enum name = s;
- alias type = F;
- }
-
- static if (members.length == 0)
- alias UniqMembers = AliasSeq!();
- else
- {
- alias func = members[0];
- enum name = __traits(identifier, func);
- alias type = FunctionTypeOf!func;
- template check(size_t i, mem...)
- {
- static if (i >= mem.length)
- enum ptrdiff_t check = -1;
- else static if
- (__traits(identifier, func) == __traits(identifier, mem[i]) &&
- !is(DerivedFunctionType!(type, FunctionTypeOf!(mem[i])) == void))
- {
- enum ptrdiff_t check = i;
- }
- else
- enum ptrdiff_t check = check!(i + 1, mem);
- }
- enum ptrdiff_t x = 1 + check!(0, members[1 .. $]);
- static if (x >= 1)
- {
- alias typex = DerivedFunctionType!(type, FunctionTypeOf!(members[x]));
- alias remain = UniqMembers!(members[1 .. x], members[x + 1 .. $]);
-
- static if (remain.length >= 1 && remain[0].name == name &&
- !is(DerivedFunctionType!(typex, remain[0].type) == void))
- {
- alias F = DerivedFunctionType!(typex, remain[0].type);
- alias UniqMembers = AliasSeq!(FuncInfo!(name, F), remain[1 .. $]);
- }
- else
- alias UniqMembers = AliasSeq!(FuncInfo!(name, typex), remain);
- }
- else
- {
- alias UniqMembers = AliasSeq!(FuncInfo!(name, type), UniqMembers!(members[1 .. $]));
- }
- }
-}
-
-// find a function from Fs that has same identifier and covariant type with f
-private template findCovariantFunction(alias finfo, Source, Fs...)
-{
- template check(size_t i = 0)
- {
- static if (i >= Fs.length)
- enum ptrdiff_t check = -1;
- else
- {
- enum ptrdiff_t check =
- (finfo.name == __traits(identifier, Fs[i])) &&
- isCovariantWith!(FunctionTypeOf!(Fs[i]), finfo.type)
- ? i : check!(i + 1);
- }
- }
- enum x = check!();
- static if (x == -1 && is(typeof(Source.opDispatch)))
- {
- alias Params = Parameters!(finfo.type);
- enum ptrdiff_t findCovariantFunction =
- is(typeof(( Source).init.opDispatch!(finfo.name)(Params.init))) ||
- is(typeof(( const Source).init.opDispatch!(finfo.name)(Params.init))) ||
- is(typeof(( immutable Source).init.opDispatch!(finfo.name)(Params.init))) ||
- is(typeof(( shared Source).init.opDispatch!(finfo.name)(Params.init))) ||
- is(typeof((shared const Source).init.opDispatch!(finfo.name)(Params.init)))
- ? ptrdiff_t.max : -1;
- }
- else
- enum ptrdiff_t findCovariantFunction = x;
-}
-
-/**
-Type constructor for final (aka head-const) variables.
-
-Final variables cannot be directly mutated or rebound, but references
-reached through the variable are typed with their original mutability.
-It is equivalent to `final` variables in D1 and Java, as well as
-`readonly` variables in C#.
-
-When `T` is a `const` or `immutable` type, `Final` aliases
-to `T`.
-*/
-template Final(T)
-{
-static if (is(T == const) || is(T == immutable))
- alias Final = T;
-else
-{
- struct Final
- {
- import std.typecons : Proxy;
-
- private T final_value;
- mixin Proxy!final_value;
-
- /**
- * Construction is forwarded to the underlying type.
- */
- this(T other)
- {
- this.final_value = other;
- }
-
- /// Ditto
- this(Args...)(auto ref Args args)
- if (__traits(compiles, T(args)))
- {
- static assert((!is(T == struct) && !is(T == union)) || !isNested!T,
- "Non-static nested type " ~ fullyQualifiedName!T ~ " must be " ~
- "constructed explicitly at the call-site (e.g. auto s = " ~
- "makeFinal(" ~ T.stringof ~ "(...));)");
- this.final_value = T(args);
- }
-
- // Attaching function attributes gives less noisy error messages
- pure nothrow @safe @nogc
- {
- /++
- + All operators, including member access, are forwarded to the
- + underlying value of type `T` except for these mutating operators,
- + which are disabled.
- +/
- void opAssign(Other)(Other other)
- {
- static assert(0, typeof(this).stringof ~
- " cannot be reassigned.");
- }
-
- /// Ditto
- void opOpAssign(string op, Other)(Other other)
- {
- static assert(0, typeof(this).stringof ~
- " cannot be reassigned.");
- }
-
- /// Ditto
- void opUnary(string op : "--")()
- {
- static assert(0, typeof(this).stringof ~
- " cannot be mutated.");
- }
-
- /// Ditto
- void opUnary(string op : "++")()
- {
- static assert(0, typeof(this).stringof ~
- " cannot be mutated.");
- }
- }
-
- /**
- *
- * `Final!T` implicitly converts to an rvalue of type `T` through
- * `AliasThis`.
- */
- inout(T) final_get() inout
- {
- return final_value;
- }
-
- /// Ditto
- alias final_get this;
-
- /// Ditto
- auto ref opUnary(string op)()
- if (__traits(compiles, mixin(op ~ "T.init")))
- {
- return mixin(op ~ "this.final_value");
- }
- }
-}
-}
-
-/// Ditto
-Final!T makeFinal(T)(T t)
-{
- return Final!T(t);
-}
-
-/// `Final` can be used to create class references which cannot be rebound:
-pure nothrow @safe unittest
-{
- static class A
- {
- int i;
-
- this(int i) pure nothrow @nogc @safe
- {
- this.i = i;
- }
- }
-
- auto a = makeFinal(new A(42));
- assert(a.i == 42);
-
- //a = new A(24); // Reassignment is illegal,
- a.i = 24; // But fields are still mutable.
-
- assert(a.i == 24);
-}
-
-/// `Final` can also be used to create read-only data fields without using transitive immutability:
-pure nothrow @safe unittest
-{
- static class A
- {
- int i;
-
- this(int i) pure nothrow @nogc @safe
- {
- this.i = i;
- }
- }
-
- static class B
- {
- Final!A a;
-
- this(A a) pure nothrow @nogc @safe
- {
- this.a = a; // Construction, thus allowed.
- }
- }
-
- auto b = new B(new A(42));
- assert(b.a.i == 42);
-
- // b.a = new A(24); // Reassignment is illegal,
- b.a.i = 24; // but `a` is still mutable.
-
- assert(b.a.i == 24);
-}
-
-pure nothrow @safe unittest
-{
- static class A { int i; }
- static assert(!is(Final!A == A));
- static assert(is(Final!(const A) == const A));
- static assert(is(Final!(immutable A) == immutable A));
-
- Final!A a = new A;
- static assert(!__traits(compiles, a = new A));
-
- assert(a.i == 0);
- a.i = 42;
- assert(a.i == 42);
-
- Final!int i = 42;
- static assert(!__traits(compiles, i = 24));
- static assert(!__traits(compiles, --i));
- static assert(!__traits(compiles, ++i));
- assert(i == 42);
- int iCopy = i;
- assert(iCopy == 42);
- iCopy = -i; // non-mutating unary operators must work
- assert(iCopy == -42);
-
- static struct S
- {
- int i;
-
- pure nothrow @safe @nogc:
- this(int i){}
- this(string s){}
- this(int i, string s, float f){ this.i = i; }
- }
-
- Final!S sint = 42;
- Final!S sstr = "foo";
- static assert(!__traits(compiles, sint = sstr));
-
- auto sboth = Final!S(42, "foo", 3.14);
- assert(sboth.i == 42);
-
- sboth.i = 24;
- assert(sboth.i == 24);
-
- struct NestedS
- {
- int i;
- int get() pure nothrow @safe @nogc { return sboth.i + i; }
- }
-
- // Nested structs must be constructed at the call-site
- static assert(!__traits(compiles, Final!NestedS(6)));
- auto s = makeFinal(NestedS(6));
- assert(s.i == 6);
- assert(s.get == 30);
-
- class NestedC
- {
- int i;
-
- pure nothrow @safe @nogc:
- this(int i) { this.i = i; }
- int get() { return sboth.i + i; }
- }
-
- auto c = makeFinal(new NestedC(6));
- assert(c.i == 6);
- assert(c.get == 30);
-}
-
-pure nothrow @safe unittest
-{
- auto arr = makeFinal([1, 2, 3]);
- static assert(!__traits(compiles, arr = null));
- static assert(!__traits(compiles, arr ~= 4));
- assert((arr ~ 4) == [1, 2, 3, 4]);
-}
-
-// https://issues.dlang.org/show_bug.cgi?id=17270
-pure nothrow @nogc @system unittest
-{
- int i = 1;
- Final!(int*) fp = &i;
- assert(*fp == 1);
- static assert(!__traits(compiles,
- fp = &i // direct assignment
- ));
- static assert(is(typeof(*fp) == int));
- *fp = 2; // indirect assignment
- assert(*fp == 2);
- int* p = fp;
- assert(*p == 2);
-}
-
-pure nothrow @system unittest
-{
- Final!(int[]) arr;
- // static assert(!__traits(compiles,
- // arr.length = 10; // bug!
- // ));
- static assert(!__traits(compiles,
- arr.ptr = null
- ));
- static assert(!__traits(compiles,
- arr.ptr++
- ));
-}
diff --git a/std/file.d b/std/file.d
index b3dc4e8d1a5..88755dc7b0c 100644
--- a/std/file.d
+++ b/std/file.d
@@ -3714,7 +3714,7 @@ assert(!de2.isFile);
@property bool isSymlink() scope;
/++
- Returns the size of the the file represented by this `DirEntry`
+ Returns the size of the file represented by this `DirEntry`
in bytes.
+/
@property ulong size() scope;
@@ -4465,9 +4465,10 @@ void rmdirRecurse(ref scope DirEntry de) @safe
}
else
{
- // dirEntries is @system because it uses a DirIterator with a
- // RefCounted variable, but here, no references to the payload is
- // escaped to the outside, so this should be @trusted
+ // dirEntries is @system without DIP1000 because it uses
+ // a DirIterator with a SafeRefCounted variable, but here, no
+ // references to the payload are escaped to the outside, so this should
+ // be @trusted
() @trusted {
// all children, recursively depth-first
foreach (DirEntry e; dirEntries(de.name, SpanMode.depth, false))
@@ -4866,21 +4867,32 @@ private struct DirIteratorImpl(alias pred = (const scope ref DirEntry entry) =>
}
}
-struct DirIterator(alias pred = (const scope ref DirEntry entry) => true)
+struct _DirIterator(bool useDIP1000, alias pred = (const scope ref DirEntry entry) => true)
// TODO: if (is(typeof(pred(DirEntry.init)) : bool))
+// Must be a template, because the destructor is unsafe or safe depending on
+// whether `-preview=dip1000` is in use. Otherwise, linking errors would
+// result.
{
-@safe:
+ static assert(useDIP1000 == dip1000Enabled,
+ "Please don't override useDIP1000 to disagree with compiler switch.");
+
private:
- RefCounted!(DirIteratorImpl!(pred), RefCountedAutoInitialize.no) impl;
+ SafeRefCounted!(DirIteratorImpl!(pred), RefCountedAutoInitialize.no) impl;
+
this(string pathname, SpanMode mode, bool followSymlink) @trusted
{
impl = typeof(impl)(pathname, mode, followSymlink);
}
public:
- @property bool empty() { return impl.empty; }
- @property DirEntry front() { return impl.front; }
- void popFront() { impl.popFront(); }
+ @property bool empty() @trusted { return impl.empty; }
+ @property DirEntry front() @trusted { return impl.front; }
+ void popFront() @trusted { impl.popFront(); }
}
+
+// This has the client code to automatically use and link to the correct
+// template instance
+alias DirIterator = _DirIterator!dip1000Enabled;
+
/++
Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
of `DirEntry` that lazily iterates a given directory,
@@ -4894,6 +4906,11 @@ public:
operating system / filesystem, and may not follow any particular sorting.
Params:
+ useDIP1000 = used to instantiate this function separately for code with
+ and without -preview=dip1000 compiler switch, because it
+ affects the ABI of this function. Set automatically -
+ don't touch.
+
path = The directory to iterate over.
If empty, the current directory will be iterated.
@@ -4959,14 +4976,14 @@ foreach (d; dFiles)
writeln(d.name);
--------------------
+/
-auto dirEntries(string path, SpanMode mode, bool followSymlink = true)
-{
- return DirIterator!()(path, mode, followSymlink);
-}
-auto dirEntries(alias pred)(string path, SpanMode mode, bool followSymlink = true)
+
+// For some reason, doing the same alias-to-a-template trick as with DirIterator
+// does not work here.
+auto dirEntries(bool useDIP1000 = dip1000Enabled, alias pred = (const scope ref DirEntry entry) => true)
+ (string path, SpanMode mode, bool followSymlink = true)
// TODO: if (is(typeof(pred(DirEntry.init)) : bool))
{
- return DirIterator!(pred)(path, mode, followSymlink);
+ return _DirIterator!(useDIP1000, pred)(path, mode, followSymlink);
}
/// Duplicate functionality of D1's `std.file.listdir()`:
@@ -4985,13 +5002,14 @@ auto dirEntries(alias pred)(string path, SpanMode mode, bool followSymlink = tru
.array;
}
- void main(string[] args)
+ // Can be safe only with -preview=dip1000
+ @safe void main(string[] args)
{
import std.stdio;
string[] files = listdir(args[1]);
writefln("%s", files);
- }
+ }
}
@system unittest
@@ -5066,14 +5084,15 @@ auto dirEntries(alias pred)(string path, SpanMode mode, bool followSymlink = tru
}
/// Ditto
-auto dirEntries(string path, string pattern, SpanMode mode,
+auto dirEntries(bool useDIP1000 = dip1000Enabled)
+ (string path, string pattern, SpanMode mode,
bool followSymlink = true)
{
import std.algorithm.iteration : filter;
import std.path : globMatch, baseName;
bool f(DirEntry de) { return globMatch(baseName(de.name), pattern); }
- return filter!f(DirIterator!()(path, mode, followSymlink));
+ return filter!f(_DirIterator!(useDIP1000)(path, mode, followSymlink));
}
@safe unittest
@@ -5154,7 +5173,7 @@ auto dirEntries(string path, string pattern, SpanMode mode,
// Make sure that dirEntries does not butcher Unicode file names
// https://issues.dlang.org/show_bug.cgi?id=17962
-@system unittest
+@safe unittest
{
import std.algorithm.comparison : equal;
import std.algorithm.iteration : map;
diff --git a/std/format/package.d b/std/format/package.d
index 3f6f33adf6e..d83f0281e59 100644
--- a/std/format/package.d
+++ b/std/format/package.d
@@ -550,7 +550,7 @@ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com,
Andrei Alexandrescu), and Kenji Hara
-Source: $(PHOBOSSRC std/format.d)
+Source: $(PHOBOSSRC std/format/package.d)
*/
module std.format;
diff --git a/std/internal/math/gammafunction.d b/std/internal/math/gammafunction.d
index 7f72234d640..703ecb1e6b3 100644
--- a/std/internal/math/gammafunction.d
+++ b/std/internal/math/gammafunction.d
@@ -631,7 +631,7 @@ enum real BETA_BIGINV = 1.084202172485504434007e-19L;
* betaIncomplete(a, b, x) = Γ(a+b)/(Γ(a) Γ(b)) *
* $(INTEGRATE 0, x) $(POWER t, a-1)$(POWER (1-t),b-1) dt
*
- * and is the same as the the cumulative distribution function.
+ * and is the same as the cumulative distribution function.
*
* The domain of definition is 0 <= x <= 1. In this
* implementation a and b are restricted to positive values.
diff --git a/std/logger/core.d b/std/logger/core.d
index be2bd8d175e..846f6ee214a 100644
--- a/std/logger/core.d
+++ b/std/logger/core.d
@@ -704,7 +704,7 @@ abstract class Logger
/** This template provides the log functions for the `Logger` `class`
with the `LogLevel` encoded in the function name.
- For further information see the the two functions defined inside of this
+ For further information see the two functions defined inside of this
template.
The aliases following this template create the public names of these log
@@ -1446,7 +1446,7 @@ that the returned reference is only a current snapshot and in the following
code, you must make sure no other thread reassigns to it between reading and
writing `sharedLog`.
-`sharedLog` is only thread-safe if the the used `Logger` is thread-safe.
+`sharedLog` is only thread-safe if the used `Logger` is thread-safe.
The default `Logger` is thread-safe.
-------------
if (sharedLog !is myLogger)
@@ -1559,10 +1559,21 @@ class StdForwardLogger : Logger
}
}
+ auto oldSharedLog = sharedLog;
+
sharedLog = new shared RaceLogger;
- scope(exit) { sharedLog = null; }
- () @trusted { new Thread(() { log("foo"); }).start(); }();
+ scope(exit)
+ {
+ sharedLog = oldSharedLog;
+ }
+ Thread toWaitFor;
+ () @trusted { toWaitFor = new Thread(() { log("foo"); }).start(); }();
log("bar");
+
+ () @trusted
+ {
+ toWaitFor.join();
+ }();
}
/** This `LogLevel` is unqiue to every thread.
@@ -1897,7 +1908,7 @@ version (StdUnittest) private void testFuncNames(Logger logger) @safe
assertThrown!Throwable(logf(LogLevel.fatal, msg, "Yet"));
} ();
lineNumber = __LINE__ - 2;
- assert(l.msg == msg.format("Yet"));
+ assert(l.msg == msg.format("Yet"), l.msg);
assert(l.line == lineNumber);
assert(l.logLevel == LogLevel.all);
diff --git a/std/net/curl.d b/std/net/curl.d
index 7ea2cebb30f..8745bbd7a07 100644
--- a/std/net/curl.d
+++ b/std/net/curl.d
@@ -28,7 +28,7 @@ to your $(B dub.json) file if you are using $(LINK2 http://code.dlang.org, DUB).
Windows x86 note:
A DMD compatible libcurl static library can be downloaded from the dlang.org
-$(LINK2 http://downloads.dlang.org/other/index.html, download archive page).
+$(LINK2 https://downloads.dlang.org/other/index.html, download archive page).
This module is not available for iOS, tvOS or watchOS.
@@ -1973,7 +1973,7 @@ private mixin template Protocol()
}
/**
- * The network interface to use in form of the the IP of the interface.
+ * The network interface to use in form of the IP of the interface.
*
* Example:
* ----
@@ -2706,7 +2706,7 @@ struct HTTP
@property void dnsTimeout(Duration d);
/**
- * The network interface to use in form of the the IP of the interface.
+ * The network interface to use in form of the IP of the interface.
*
* Example:
* ----
@@ -3486,7 +3486,7 @@ struct FTP
@property void dnsTimeout(Duration d);
/**
- * The network interface to use in form of the the IP of the interface.
+ * The network interface to use in form of the IP of the interface.
*
* Example:
* ----
@@ -3912,7 +3912,7 @@ struct SMTP
@property void dnsTimeout(Duration d);
/**
- * The network interface to use in form of the the IP of the interface.
+ * The network interface to use in form of the IP of the interface.
*
* Example:
* ----
diff --git a/std/numeric.d b/std/numeric.d
index 96d20c233d8..df7ac39b4e4 100644
--- a/std/numeric.d
+++ b/std/numeric.d
@@ -1124,8 +1124,8 @@ public:
*
* References: "On Enclosing Simple Roots of Nonlinear Equations",
* G. Alefeld, F.A. Potra, Yixun Shi, Mathematics of Computation 61,
- * pp733-744 (1993). Fortran code available from $(HTTP
- * www.netlib.org,www.netlib.org) as algorithm TOMS478.
+ * pp733-744 (1993). Fortran code available from
+ * $(HTTP www.netlib.org,www.netlib.org) as algorithm TOMS478.
*
*/
T findRoot(T, DF, DT)(scope DF f, const T a, const T b,
diff --git a/std/path.d b/std/path.d
index de180fcc548..4b5a7efe07f 100644
--- a/std/path.d
+++ b/std/path.d
@@ -3357,8 +3357,10 @@ in
{
// Verify that pattern[] is valid
import std.algorithm.searching : balancedParens;
- assert(balancedParens(pattern, '[', ']', 0));
- assert(balancedParens(pattern, '{', '}', 0));
+ import std.utf : byUTF;
+
+ assert(balancedParens(pattern.byUTF!C, '[', ']', 0));
+ assert(balancedParens(pattern.byUTF!C, '{', '}', 0));
}
do
{
@@ -3959,7 +3961,7 @@ string expandTilde(string inputPath) @safe nothrow
version (Posix)
{
import core.exception : onOutOfMemoryError;
- import core.stdc.errno : errno, ERANGE;
+ import core.stdc.errno : errno, EBADF, ENOENT, EPERM, ERANGE, ESRCH;
import core.stdc.stdlib : malloc, free, realloc;
/* Joins a path from a C string to the remainder of path.
@@ -4065,7 +4067,7 @@ string expandTilde(string inputPath) @safe nothrow
char[] extra_memory;
passwd result;
- while (1)
+ loop: while (1)
{
extra_memory.length += extra_memory_size;
@@ -4088,10 +4090,23 @@ string expandTilde(string inputPath) @safe nothrow
break;
}
- if (errno != ERANGE &&
+ switch (errno)
+ {
+ case ERANGE:
// On BSD and OSX, errno can be left at 0 instead of set to ERANGE
- errno != 0)
- onOutOfMemoryError();
+ case 0:
+ break;
+
+ case ENOENT:
+ case ESRCH:
+ case EBADF:
+ case EPERM:
+ // The given name or uid was not found.
+ break loop;
+
+ default:
+ onOutOfMemoryError();
+ }
// extra_memory isn't large enough
import core.checkedint : mulu;
diff --git a/std/range/primitives.d b/std/range/primitives.d
index 31f58fa5fa9..cdab401a1f2 100644
--- a/std/range/primitives.d
+++ b/std/range/primitives.d
@@ -172,7 +172,7 @@ Returns:
enum bool isInputRange(R) =
is(typeof(R.init) == R)
&& is(ReturnType!((R r) => r.empty) == bool)
- && is(typeof((return ref R r) => r.front))
+ && (is(typeof((return ref R r) => r.front)) || is(typeof(ref (return ref R r) => r.front)))
&& !is(ReturnType!((R r) => r.front) == void)
&& is(typeof((R r) => r.popFront));
@@ -227,6 +227,17 @@ enum bool isInputRange(R) =
}
static assert(!isInputRange!VoidFront);
}
+// https://issues.dlang.org/show_bug.cgi?id=16034
+@safe unittest
+{
+ struct One
+ {
+ int entry = 1;
+ @disable this(this);
+ }
+
+ assert(isInputRange!(One[]));
+}
@safe unittest
{
diff --git a/std/socket.d b/std/socket.d
index 6ec79858194..fb2c2d42253 100644
--- a/std/socket.d
+++ b/std/socket.d
@@ -2942,7 +2942,7 @@ public:
* Calling `shutdown` before `close` is recommended
* for connection-oriented sockets.
*/
- void close() @trusted nothrow @nogc
+ void close() scope @trusted nothrow @nogc
{
_close(sock);
sock = socket_t.init;
@@ -3641,7 +3641,7 @@ class UdpSocket: Socket
{
checkAttributes!q{nothrow @nogc @trusted};
}
- nothrow @nogc @trusted void close()
+ nothrow @nogc @trusted scope void close()
{
checkAttributes!q{nothrow @nogc @trusted};
}
diff --git a/std/stdio.d b/std/stdio.d
index a1fe962906b..fea9b4c8d58 100644
--- a/std/stdio.d
+++ b/std/stdio.d
@@ -85,8 +85,7 @@ else version (CRuntime_Musl)
}
else version (CRuntime_UClibc)
{
- // uClibc supports GCC IO
- version = GCC_IO;
+ version = GENERIC_IO;
}
else version (OSX)
{
@@ -589,7 +588,7 @@ Throws: `ErrnoException` if the file could not be opened.
detach();
}
- this(this) @safe nothrow
+ this(this) @safe pure nothrow @nogc
{
if (!_p) return;
assert(atomicLoad(_p.refs));
@@ -5211,7 +5210,7 @@ enum StdFileHandle: string
{
with (StdFileHandle)
assert(_iob == stdin || _iob == stdout || _iob == stderr);
- impl.handle = mixin(_iob);
+ impl.handle = cast() mixin(_iob);
result._p = &impl;
atomicOp!"+="(spinlock, uint.max / 2);
break;
diff --git a/std/string.d b/std/string.d
index 5c9a2c946ba..7ed24f7f98d 100644
--- a/std/string.d
+++ b/std/string.d
@@ -6884,7 +6884,7 @@ if (isSomeString!S)
if (inword)
{
- if (col + 1 + (s.length - wordstart) >= columns)
+ if (col + 1 + (s.length - wordstart) > columns)
{
result ~= '\n';
result ~= indent;
@@ -6929,6 +6929,13 @@ if (isSomeString!S)
});
}
+@safe pure unittest // https://issues.dlang.org/show_bug.cgi?id=23298
+{
+ assert("1 2 3 4 5 6 7 8 9".wrap(17) == "1 2 3 4 5 6 7 8 9\n");
+ assert("1 2 3 4 5 6 7 8 9 ".wrap(17) == "1 2 3 4 5 6 7 8 9\n");
+ assert("1 2 3 4 5 6 7 8 99".wrap(17) == "1 2 3 4 5 6 7 8\n99\n");
+}
+
/******************************************
* Removes one level of indentation from a multi-line string.
*
diff --git a/std/sumtype.d b/std/sumtype.d
index 160665c6a31..ee2d73a1233 100644
--- a/std/sumtype.d
+++ b/std/sumtype.d
@@ -753,23 +753,6 @@ public:
}
}
- invariant
- {
- this.match!((ref value) {
- static if (is(typeof(value) == class))
- {
- if (value !is null)
- {
- assert(value);
- }
- }
- else static if (is(typeof(value) == struct))
- {
- assert(&value);
- }
- });
- }
-
// Workaround for https://issues.dlang.org/show_bug.cgi?id=21400
version (StdDdoc)
{
@@ -1330,36 +1313,6 @@ version (D_BetterC) {} else
}));
}
-// Types with invariants
-// Disabled in BetterC due to use of exceptions
-version (D_BetterC) {} else
-version (D_Invariants)
-@system unittest
-{
- import std.exception : assertThrown;
- import core.exception : AssertError;
-
- struct S
- {
- int i;
- invariant { assert(i >= 0); }
- }
-
- class C
- {
- int i;
- invariant { assert(i >= 0); }
- }
-
- SumType!S x;
- x.match!((ref v) { v.i = -1; });
- assertThrown!AssertError(assert(&x));
-
- SumType!C y = new C();
- y.match!((ref v) { v.i = -1; });
- assertThrown!AssertError(assert(&y));
-}
-
// Calls value postblit on self-assignment
@safe unittest
{
diff --git a/std/traits.d b/std/traits.d
index cc08ea638be..1cbff8414e3 100644
--- a/std/traits.d
+++ b/std/traits.d
@@ -9136,3 +9136,11 @@ package(std) template DeducedParameterType(T)
{
static assert(is(DeducedParameterType!(inout(int[])) == inout(int)[]));
}
+
+private auto dip1000Test(int x) {return *&x;}
+// We don't use isSafe, because betterC client code needs to instantiate
+// core.internal.array.comparison.__cmp in the client side. isSafe uses
+// __cmp of two strings, so using it would instantate that here instead. That
+// won't do because betterC compilations do not link the Phobos binary in.
+package(std) enum dip1000Enabled
+ = is(typeof(&dip1000Test) : int function(int) @safe);
diff --git a/std/typecons.d b/std/typecons.d
index c7cdc24e4d5..5b60679bb02 100644
--- a/std/typecons.d
+++ b/std/typecons.d
@@ -22,8 +22,8 @@ $(TR $(TD Flags) $(TD
$(LREF Yes)
))
$(TR $(TD Memory allocation) $(TD
- $(LREF RefCounted)
- $(LREF refCounted)
+ $(LREF SafeRefCounted)
+ $(LREF safeRefCounted)
$(LREF RefCountedAutoInitialize)
$(LREF scoped)
$(LREF Unique)
@@ -2759,6 +2759,9 @@ Nullable!T) object starts in the null state. Assigning it renders it
non-null. Calling `nullify` can nullify it again.
Practically `Nullable!T` stores a `T` and a `bool`.
+
+See also:
+ $(LREF apply), an alternative way to use the payload.
*/
struct Nullable(T)
{
@@ -3793,8 +3796,28 @@ Params:
sink.formatValue(_value, fmt);
}
}
+
+ void toString()(scope void delegate(const(char)[]) sink, scope const ref FormatSpec!char fmt) const
+ {
+ if (isNull)
+ {
+ sink.formatValue("Nullable.null", fmt);
+ }
+ else
+ {
+ sink.formatValue(_value, fmt);
+ }
+ }
}
+@system unittest
+{
+ import std.conv : to;
+
+ const Nullable!(ulong, 0) x = 1;
+ assert(x.to!string == "1");
+}
+
/**
Check if `this` is in the null state.
@@ -4158,7 +4181,7 @@ Returns:
See also:
$(HTTPS en.wikipedia.org/wiki/Monad_(functional_programming)#The_Maybe_monad, The `Maybe` monad)
- */
+*/
template apply(alias fun)
{
import std.functional : unaryFun;
@@ -4320,8 +4343,28 @@ Params:
sink.formatValue(*_value, fmt);
}
}
+
+ void toString()(scope void delegate(const(char)[]) sink, scope const ref FormatSpec!char fmt) const
+ {
+ if (isNull)
+ {
+ sink.formatValue("Nullable.null", fmt);
+ }
+ else
+ {
+ sink.formatValue(*_value, fmt);
+ }
+ }
}
+@system unittest
+{
+ import std.conv : to;
+
+ const NullableRef!(ulong) x = new ulong(1);
+ assert(x.to!string == "1");
+}
+
/**
Binds the internal state to `value`.
@@ -6510,8 +6553,8 @@ package template Bind(alias Template, args1...)
/**
-Options regarding auto-initialization of a `RefCounted` object (see
-the definition of `RefCounted` below).
+Options regarding auto-initialization of a `SafeRefCounted` object (see
+the definition of `SafeRefCounted` below).
*/
enum RefCountedAutoInitialize
{
@@ -6532,6 +6575,27 @@ enum RefCountedAutoInitialize
int a = 42;
}
+ SafeRefCounted!(Foo, RefCountedAutoInitialize.yes) rcAuto;
+ SafeRefCounted!(Foo, RefCountedAutoInitialize.no) rcNoAuto;
+
+ assert(rcAuto.refCountedPayload.a == 42);
+
+ assertThrown!AssertError(rcNoAuto.refCountedPayload);
+ rcNoAuto.refCountedStore.ensureInitialized;
+ assert(rcNoAuto.refCountedPayload.a == 42);
+}
+
+// Same the above but for old RefCounted and not documented
+@system unittest
+{
+ import core.exception : AssertError;
+ import std.exception : assertThrown;
+
+ struct Foo
+ {
+ int a = 42;
+ }
+
RefCounted!(Foo, RefCountedAutoInitialize.yes) rcAuto;
RefCounted!(Foo, RefCountedAutoInitialize.no) rcNoAuto;
@@ -6546,16 +6610,16 @@ enum RefCountedAutoInitialize
Defines a reference-counted object containing a `T` value as
payload.
-An instance of `RefCounted` is a reference to a structure,
+An instance of `SafeRefCounted` is a reference to a structure,
which is referred to as the $(I store), or $(I storage implementation
struct) in this documentation. The store contains a reference count
-and the `T` payload. `RefCounted` uses `malloc` to allocate
-the store. As instances of `RefCounted` are copied or go out of
+and the `T` payload. `SafeRefCounted` uses `malloc` to allocate
+the store. As instances of `SafeRefCounted` are copied or go out of
scope, they will automatically increment or decrement the reference
-count. When the reference count goes down to zero, `RefCounted`
+count. When the reference count goes down to zero, `SafeRefCounted`
will call `destroy` against the payload and call `free` to
deallocate the store. If the `T` payload contains any references
-to GC-allocated memory, then `RefCounted` will add it to the GC memory
+to GC-allocated memory, then `SafeRefCounted` will add it to the GC memory
that is scanned for pointers, and remove it from GC scanning before
`free` is called on the store.
@@ -6567,8 +6631,15 @@ still be valid during the destructor call. This allows the `T` to
deallocate or clean up any non-GC resources immediately after the
reference count has reached zero.
-`RefCounted` is unsafe and should be used with care. No references
-to the payload should be escaped outside the `RefCounted` object.
+Without -preview=dip1000, `SafeRefCounted` is unsafe and should be
+used with care. No references to the payload should be escaped outside
+the `SafeRefCounted` object.
+
+With -preview=dip1000, `SafeRefCounted` is safe if it's payload is accessed only
+with the $(LREF borrow) function. Scope semantics can also prevent accidental
+escaping of `refCountedPayload`, but it's still up to the user to not destroy
+the last counted reference while the payload is in use. Due to that,
+`refCountedPayload` remains accessible only in `@system` code.
The `autoInit` option makes the object ensure the store is
automatically initialized. Leaving $(D autoInit ==
@@ -6581,8 +6652,11 @@ pointer dereference.
If `T.this()` is annotated with `@disable` then `autoInit` must be
`RefCountedAutoInitialize.no` in order to compile.
+
+See_Also:
+ $(LREF RefCounted)
*/
-struct RefCounted(T, RefCountedAutoInitialize autoInit =
+struct SafeRefCounted(T, RefCountedAutoInitialize autoInit =
RefCountedAutoInitialize.yes)
if (!is(T == class) && !(is(T == interface)))
{
@@ -6602,7 +6676,20 @@ if (!is(T == class) && !(is(T == interface)))
import core.memory : GC;
}
- /// `RefCounted` storage implementation.
+ pragma(inline, true) private void checkInit()()
+ if (autoInit == RefCountedAutoInitialize.yes)
+ {
+ _refCounted.ensureInitialized();
+ }
+
+ pragma(inline, true) private void checkInit()() inout
+ if (autoInit == RefCountedAutoInitialize.no)
+ {
+ assert(_refCounted.isInitialized,
+ "Attempted to use an uninitialized payload.");
+ }
+
+ /// `SafeRefCounted` storage implementation.
struct RefCountedStore
{
private struct Impl
@@ -6618,7 +6705,7 @@ if (!is(T == class) && !(is(T == interface)))
import core.lifetime : emplace, forward;
allocateStore();
- version (D_Exceptions) scope(failure) deallocateStore();
+ version (D_Exceptions) scope(failure) () @trusted { deallocateStore(); }();
emplace(&_store._payload, forward!args);
_store._count = 1;
}
@@ -6628,7 +6715,7 @@ if (!is(T == class) && !(is(T == interface)))
import std.algorithm.mutation : moveEmplace;
allocateStore();
- moveEmplace(source, _store._payload);
+ () @trusted { moveEmplace(source, _store._payload); }();
_store._count = 1;
}
@@ -6638,13 +6725,15 @@ if (!is(T == class) && !(is(T == interface)))
static if (enableGCScan)
{
import std.internal.memory : enforceCalloc;
- _store = cast(Impl*) enforceCalloc(1, Impl.sizeof);
- GC.addRange(&_store._payload, T.sizeof);
+ auto ptr = enforceCalloc(1, Impl.sizeof);
+ _store = () @trusted { return cast(Impl*) ptr; }();
+ () @trusted { GC.addRange(&_store._payload, T.sizeof); }();
}
else
{
import std.internal.memory : enforceMalloc;
- _store = cast(Impl*) enforceMalloc(Impl.sizeof);
+ auto ptr = enforceMalloc(Impl.sizeof);
+ _store = () @trusted { return cast(Impl*) ptr; }();
}
}
@@ -6685,6 +6774,7 @@ if (!is(T == class) && !(is(T == interface)))
This function is unavailable if `T.this()` is annotated with
`@disable`.
*/
+ @safe pure nothrow
void ensureInitialized()()
{
// By checking for `@disable this()` and failing early we can
@@ -6723,7 +6813,7 @@ Postcondition: `refCountedStore.isInitialized`
}
/// Ditto
- this(T val)
+ this(return scope T val)
{
_refCounted.move(val);
}
@@ -6746,18 +6836,30 @@ to deallocate the corresponding resource.
*/
~this()
{
+ import std.traits : dip1000Enabled;
+
+ // This prevents the same reference from decrementing the count twice.
+ scope(exit) _refCounted = _refCounted.init;
+
if (!_refCounted.isInitialized) return;
assert(_refCounted._store._count > 0);
- if (--_refCounted._store._count)
- return;
+ if (--_refCounted._store._count) return;
// Done, destroy and deallocate
.destroy(_refCounted._store._payload);
- _refCounted.deallocateStore();
+
+ static if (dip1000Enabled)
+ {
+ () @trusted { _refCounted.deallocateStore(); }();
+ }
+ else _refCounted.deallocateStore();
}
/**
-Assignment operators
- */
+Assignment operators.
+
+Note: You may not assign a new payload to an uninitialized SafeRefCounted, if
+auto initialization is off. Assigning another counted reference is still okay.
+*/
void opAssign(typeof(this) rhs)
{
import std.algorithm.mutation : swap;
@@ -6770,14 +6872,7 @@ Assignment operators
{
import std.algorithm.mutation : move;
- static if (autoInit == RefCountedAutoInitialize.yes)
- {
- _refCounted.ensureInitialized();
- }
- else
- {
- assert(_refCounted.isInitialized);
- }
+ checkInit();
move(rhs, _refCounted._store._payload);
}
@@ -6789,20 +6884,20 @@ Assignment operators
RefCountedAutoInitialize.yes), calls $(D
refCountedStore.ensureInitialized). Otherwise, just issues $(D
assert(refCountedStore.isInitialized)). Used with $(D alias
- refCountedPayload this;), so callers can just use the `RefCounted`
+ refCountedPayload this;), so callers can just use the `SafeRefCounted`
object as a `T`.
$(BLUE The first overload exists only if $(D autoInit == RefCountedAutoInitialize.yes).)
So if $(D autoInit == RefCountedAutoInitialize.no)
or called for a constant or immutable object, then
- `refCountedPayload` will also be qualified as safe and nothrow
+ `refCountedPayload` will also be qualified as nothrow
(but will still assert if not initialized).
*/
- @property @trusted
+ @property @system
ref T refCountedPayload() return;
/// ditto
- @property nothrow @safe pure @nogc
+ @property nothrow @system pure @nogc
ref inout(T) refCountedPayload() inout return;
}
else
@@ -6810,19 +6905,21 @@ Assignment operators
static if (autoInit == RefCountedAutoInitialize.yes)
{
//Can't use inout here because of potential mutation
- @property
+ @property @system
ref T refCountedPayload() return
{
- _refCounted.ensureInitialized();
+ checkInit();
return _refCounted._store._payload;
}
}
-
- @property nothrow @safe pure @nogc
- ref inout(T) refCountedPayload() inout return
+ else
{
- assert(_refCounted.isInitialized, "Attempted to access an uninitialized payload.");
- return _refCounted._store._payload;
+ @property nothrow @system pure @nogc
+ ref inout(T) refCountedPayload() inout return
+ {
+ checkInit();
+ return _refCounted._store._payload;
+ }
}
}
@@ -6858,7 +6955,7 @@ assert(refCountedStore.isInitialized)).
{
// A pair of an `int` and a `size_t` - the latter being the
// reference count - will be dynamically allocated
- auto rc1 = RefCounted!int(5);
+ auto rc1 = SafeRefCounted!int(5);
assert(rc1 == 5);
// No more allocation, add just one extra reference count
auto rc2 = rc1;
@@ -6868,46 +6965,66 @@ assert(refCountedStore.isInitialized)).
// the pair will be freed when rc1 and rc2 go out of scope
}
-pure @system unittest
+// This test can't be betterC because the test extractor won't see the private
+// `initialize` method accessed here
+pure @safe nothrow @nogc unittest
{
- RefCounted!int* p;
- {
- auto rc1 = RefCounted!int(5);
- p = &rc1;
- assert(rc1 == 5);
- assert(rc1._refCounted._store._count == 1);
- auto rc2 = rc1;
- assert(rc1._refCounted._store._count == 2);
- // Reference semantics
- rc2 = 42;
- assert(rc1 == 42);
- rc2 = rc2;
- assert(rc2._refCounted._store._count == 2);
- rc1 = rc2;
- assert(rc1._refCounted._store._count == 2);
- }
- assert(p._refCounted._store == null);
+ auto rc1 = SafeRefCounted!(int, RefCountedAutoInitialize.no)(5);
+ rc1._refCounted.initialize();
+}
- // RefCounted as a member
- struct A
+pure @system unittest
+{
+ foreach (MyRefCounted; AliasSeq!(SafeRefCounted, RefCounted))
{
- RefCounted!int x;
- this(int y)
+ MyRefCounted!int* p;
{
- x._refCounted.initialize(y);
+ auto rc1 = MyRefCounted!int(5);
+ p = &rc1;
+ assert(rc1 == 5);
+ assert(rc1._refCounted._store._count == 1);
+ auto rc2 = rc1;
+ assert(rc1._refCounted._store._count == 2);
+ // Reference semantics
+ rc2 = 42;
+ assert(rc1 == 42);
+ rc2 = rc2;
+ assert(rc2._refCounted._store._count == 2);
+ rc1 = rc2;
+ assert(rc1._refCounted._store._count == 2);
}
- A copy()
+ assert(p._refCounted._store == null);
+
+ // [Safe]RefCounted as a member
+ struct A
{
- auto another = this;
- return another;
+ MyRefCounted!int x;
+ this(int y)
+ {
+ x._refCounted.initialize(y);
+ }
+ A copy()
+ {
+ auto another = this;
+ return another;
+ }
}
- }
- auto a = A(4);
- auto b = a.copy();
- assert(a.x._refCounted._store._count == 2,
- "https://issues.dlang.org/show_bug.cgi?id=4356 still unfixed");
+ auto a = A(4);
+ auto b = a.copy();
+ assert(a.x._refCounted._store._count == 2,
+ "https://issues.dlang.org/show_bug.cgi?id=4356 still unfixed");
+ }
+}
+
+@betterC pure @safe nothrow @nogc unittest
+{
+ import std.algorithm.mutation : swap;
+
+ SafeRefCounted!int p1, p2;
+ swap(p1, p2);
}
+// Same as above but for old RefCounted and not @safe
@betterC pure @system nothrow @nogc unittest
{
import std.algorithm.mutation : swap;
@@ -6928,25 +7045,52 @@ pure @system unittest
U u;
}
+ alias SRC = SafeRefCounted!S;
+}
+
+// Same as above but for old RefCounted and not @safe
+@betterC @system pure nothrow @nogc unittest
+{
+ union U {
+ size_t i;
+ void* p;
+ }
+
+ struct S {
+ U u;
+ }
+
alias SRC = RefCounted!S;
}
// https://issues.dlang.org/show_bug.cgi?id=6436
@betterC @system pure unittest
{
+ import std.meta : AliasSeq;
struct S
{
this(int rval) { assert(rval == 1); }
this(ref int lval) { assert(lval == 3); ++lval; }
}
- auto s1 = RefCounted!S(1);
- int lval = 3;
- auto s2 = RefCounted!S(lval);
- assert(lval == 4);
+ foreach (MyRefCounted; AliasSeq!(SafeRefCounted, RefCounted))
+ {
+ auto s1 = MyRefCounted!S(1);
+ int lval = 3;
+ auto s2 = MyRefCounted!S(lval);
+ assert(lval == 4);
+ }
}
// gc_addRange coverage
+@betterC @safe pure unittest
+{
+ struct S { int* p; }
+
+ auto s = SafeRefCounted!S(null);
+}
+
+// Same as above but for old RefCounted and not @safe
@betterC @system pure unittest
{
struct S { int* p; }
@@ -6956,69 +7100,253 @@ pure @system unittest
@betterC @system pure nothrow @nogc unittest
{
- RefCounted!int a;
- a = 5; //This should not assert
- assert(a == 5);
+ import std.meta : AliasSeq;
+ foreach (MyRefCounted; AliasSeq!(SafeRefCounted, RefCounted))
+ {
+ MyRefCounted!int a;
+ a = 5; //This should not assert
+ assert(a == 5);
- RefCounted!int b;
- b = a; //This should not assert either
- assert(b == 5);
+ MyRefCounted!int b;
+ b = a; //This should not assert either
+ assert(b == 5);
- RefCounted!(int*) c;
+ MyRefCounted!(int*) c;
+ }
}
// https://issues.dlang.org/show_bug.cgi?id=21638
@betterC @system pure nothrow @nogc unittest
{
+ import std.meta : AliasSeq;
static struct NoDefaultCtor
{
@disable this();
this(int x) @nogc nothrow pure { this.x = x; }
int x;
}
- auto rc = RefCounted!(NoDefaultCtor, RefCountedAutoInitialize.no)(5);
- assert(rc.x == 5);
+
+ foreach (MyRefCounted; AliasSeq!(SafeRefCounted, RefCounted))
+ {
+ auto rc = MyRefCounted!(NoDefaultCtor, RefCountedAutoInitialize.no)(5);
+ assert(rc.x == 5);
+ }
}
// https://issues.dlang.org/show_bug.cgi?id=20502
@system unittest
{
- import std.conv : to;
- // Check that string conversion is transparent for refcounted
- // structs that do not have either toString or alias this.
- static struct A { Object a; }
- auto a = A(new Object());
- auto r = refCounted(a);
- assert(to!string(r) == to!string(a));
- assert(to!string(cast(const) r) == to!string(cast(const) a));
- // Check that string conversion is still transparent for refcounted
- // structs that have alias this.
- static struct B { int b; alias b this; }
- static struct C { B b; alias b this; }
- assert(to!string(refCounted(C(B(123)))) == to!string(C(B(123))));
- // https://issues.dlang.org/show_bug.cgi?id=22093
- // Check that uninitialized refcounted structs that previously could be
- // converted to strings still can be.
- alias R = typeof(r);
- R r2;
- cast(void) (((const ref R a) => to!string(a))(r2));
- cast(void) to!string(RefCounted!(A, RefCountedAutoInitialize.no).init);
+ alias Types = AliasSeq!(SafeRefCounted, RefCounted);
+ alias funcs = AliasSeq!(safeRefCounted, refCounted);
+ static foreach (aliasI; 0 .. 2)
+ {{
+ alias MyRefCounted = Types[aliasI];
+ alias myRefCounted = funcs[aliasI];
+ import std.conv : to;
+
+ // Check that string conversion is transparent for refcounted
+ // structs that do not have either toString or alias this.
+ static struct A { Object a; }
+ auto a = A(new Object());
+ auto r = myRefCounted(a);
+ assert(to!string(r) == to!string(a));
+ assert(to!string(cast(const) r) == to!string(cast(const) a));
+ // Check that string conversion is still transparent for refcounted
+ // structs that have alias this.
+ static struct B { int b; alias b this; }
+ static struct C { B b; alias b this; }
+ assert(to!string(myRefCounted(C(B(123)))) == to!string(C(B(123))));
+ // https://issues.dlang.org/show_bug.cgi?id=22093
+ // Check that uninitialized refcounted structs that previously could be
+ // converted to strings still can be.
+ alias R = typeof(r);
+ R r2;
+ cast(void) (((const ref R a) => to!string(a))(r2));
+ cast(void) to!string(MyRefCounted!(A, RefCountedAutoInitialize.no).init);
+ }}
+}
+
+// We tried to make `refCountedPayload` `@safe` in
+// https://github.com/dlang/phobos/pull/8368 . It proved impossible, but it may
+// be possible in the future. This test checks for false `@safe` issues we
+// encountered.
+@safe unittest
+{
+ struct Container
+ {
+ int[] data;
+ }
+
+ int[] getArr1 (scope Container local)
+ {
+ // allowed because the argument is inferred as return scope.
+ return local.data;
+ }
+
+ int[] getArr2 (scope Container local)
+ {
+ SafeRefCounted!Container rc = local;
+ // Escapes a reference to expired reference counted struct
+ // don't do this!
+ return rc.refCountedPayload().data;
+ }
+
+ int destroyFirstAndUseLater()
+ {
+ auto rc = SafeRefCounted!int(123);
+ int* ptr = &rc.refCountedPayload();
+ destroy(rc);
+ return *ptr;
+ }
+
+ // This is here mainly to test that safety gets inferred correctly for the
+ // next tests
+ static assert(isSafe!getArr1);
+ // https://github.com/dlang/phobos/pull/8101#issuecomment-843017282
+ // This got apparently fixed automatically by compiler updates.
+ static assert(!isSafe!getArr2);
+ // As of writing, this is the issue that is still preventing payload access
+ // from being `@safe`
+ static assert(!isSafe!destroyFirstAndUseLater);
+}
+
+/**
+Borrows the payload of $(LREF SafeRefCounted) for use in `fun`. Inferred as `@safe`
+if `fun` is `@safe` and does not escape a reference to the payload.
+The reference count will be incremented for the duration of the operation,
+so destroying the last reference will not leave dangling references in
+`fun`.
+
+Params:
+ fun = A callable accepting the payload either by value or by reference.
+ refCount = The counted reference to the payload.
+Returns:
+ The return value of `fun`, if any. `ref` in the return value will be
+ forwarded.
+Issues:
+ For yet unknown reason, code that uses this function with UFCS syntax
+ will not be inferred as `@safe`. It will still compile if the code is
+ explicitly marked `@safe` and nothing in `fun` prevents that.
+*/
+template borrow(alias fun)
+{
+ import std.functional : unaryFun;
+
+ auto ref borrow(RC)(RC refCount) if
+ (
+ isInstanceOf!(SafeRefCounted, RC)
+ && is(typeof(unaryFun!fun(refCount.refCountedPayload)))
+ )
+ {
+ refCount.checkInit();
+
+ // If `fun` escapes a reference to the payload, it will be inferred
+ // as unsafe due to the scope storage class here.
+ scope plPtr = &refCount._refCounted._store._payload;
+ return unaryFun!fun(*plPtr);
+
+ // We destroy our copy of the reference here, automatically destroying
+ // the payload if `fun` destroyed the last reference outside.
+ }
+}
+
+/// This example can be marked `@safe` with `-preview=dip1000`.
+@safe pure nothrow unittest
+{
+ auto rcInt = safeRefCounted(5);
+ assert(rcInt.borrow!(theInt => theInt) == 5);
+ auto sameInt = rcInt;
+ assert(sameInt.borrow!"a" == 5);
+
+ // using `ref` in the function
+ auto arr = [0, 1, 2, 3, 4, 5, 6];
+ sameInt.borrow!(ref (x) => arr[x]) = 10;
+ assert(arr == [0, 1, 2, 3, 4, 10, 6]);
+
+ // modifying the payload via an alias
+ sameInt.borrow!"a*=2";
+ assert(rcInt.borrow!"a" == 10);
+}
+
+// Some memory safety penetration testing.
+@system unittest
+{
+ int* globalPtr;
+ int torpedoesFired = 0;
+ struct Destroyer { ~this() @safe { torpedoesFired++; } }
+
+ alias RcInt = typeof(safeRefCounted(0));
+ auto standardUsage(RcInt arg)
+ {
+ return borrow!((ref x) => x)(arg);
+ }
+ ref harmlessRefReturn(RcInt arg)
+ {
+ return borrow!(ref (ref x) => *globalPtr = x)(arg);
+ }
+ ref problematicRefReturn(RcInt arg)
+ {
+ return borrow!(ref (ref x) => x)(arg);
+ }
+ auto sideChannelEscape(RcInt arg)
+ {
+ return borrow!((ref x)
+ {
+ globalPtr = &x;
+ return x;
+ })(arg);
+ }
+ auto destroyDuringApply()
+ {
+ auto rc = safeRefCounted(Destroyer());
+ return borrow!((ref x)
+ {
+ // Destroys the last reference to the payload, decrementing it's
+ // reference count.
+ rc.__dtor();
+ // Destroy again! rc should be set to it's init value so that this
+ // won't decrement the reference count of the original payload.
+ rc.__dtor();
+ // The internal reference count increment done for duration of
+ // `apply` should make sure that the payload destructor is not yet
+ // run, and this value thus not incremented.
+ return torpedoesFired;
+ })(rc);
+ }
+
+ // First, let's verify the dangerous functions really do what they are
+ // supposed to do.
+ auto testRc = safeRefCounted(42);
+ assert(sideChannelEscape(testRc) == 42);
+ assert(&problematicRefReturn(testRc) == globalPtr);
+
+ // Now, are the @safe attributes inferred correctly?
+ assert(isSafe!standardUsage);
+ assert(isSafe!harmlessRefReturn);
+ assert(!isSafe!problematicRefReturn);
+ assert(!isSafe!sideChannelEscape);
+ assert(isSafe!destroyDuringApply);
+
+ // Finally, we test protection against early destruction during `apply`.
+ auto torpedoesUpToReturn = destroyDuringApply();
+ assert(torpedoesFired == torpedoesUpToReturn + 1);
}
/**
- * Initializes a `RefCounted` with `val`. The template parameter
- * `T` of `RefCounted` is inferred from `val`.
+ * Initializes a `SafeRefCounted` with `val`. The template parameter
+ * `T` of `SafeRefCounted` is inferred from `val`.
* This function can be used to move non-copyable values to the heap.
- * It also disables the `autoInit` option of `RefCounted`.
+ * It also disables the `autoInit` option of `SafeRefCounted`.
*
* Params:
* val = The value to be reference counted
* Returns:
- * An initialized `RefCounted` containing `val`.
+ * An initialized `SafeRefCounted` containing `val`.
* See_Also:
+ * $(LREF refCounted)
* $(HTTP en.cppreference.com/w/cpp/memory/shared_ptr/make_shared, C++'s make_shared)
*/
-RefCounted!(T, RefCountedAutoInitialize.no) refCounted(T)(T val)
+SafeRefCounted!(T, RefCountedAutoInitialize.no) safeRefCounted(T)(T val)
{
typeof(return) res;
res._refCounted.move(val);
@@ -7043,13 +7371,13 @@ RefCounted!(T, RefCountedAutoInitialize.no) refCounted(T)(T val)
assert(File.nDestroyed == 0);
- // make the file refcounted to share ownership
+ // make the file ref counted to share ownership
// Note:
- // We write a compound statement (brace-delimited scope) in which all `RefCounted!File` handles are created and deleted.
+ // We write a compound statement (brace-delimited scope) in which all `SafeRefCounted!File` handles are created and deleted.
// This allows us to see (after the scope) what happens after all handles have been destroyed.
{
// We move the content of `file` to a separate (and heap-allocated) `File` object,
- // managed-and-accessed via one-or-multiple (initially: one) `RefCounted!File` objects ("handles").
+ // managed-and-accessed via one-or-multiple (initially: one) `SafeRefCounted!File` objects ("handles").
// This "moving":
// (1) invokes `file`'s destructor (=> `File.nDestroyed` is incremented from 0 to 1 and `file.name` becomes `null`);
// (2) overwrites `file` with `File.init` (=> `file.name` becomes `null`).
@@ -7057,18 +7385,18 @@ RefCounted!(T, RefCountedAutoInitialize.no) refCounted(T)(T val)
// but please note that (2) is only performed if `File` defines a destructor (or post-blit operator),
// and in the absence of the `nDestroyed` instrumentation there would have been no reason to define a destructor.
import std.algorithm.mutation : move;
- auto rcFile = refCounted(move(file));
+ auto rcFile = safeRefCounted(move(file));
assert(rcFile.name == "name");
assert(File.nDestroyed == 1);
assert(file.name == null);
- // We create another `RefCounted!File` handle to the same separate `File` object.
+ // We create another `SafeRefCounted!File` handle to the same separate `File` object.
// While any of the handles is still alive, the `File` object is kept alive (=> `File.nDestroyed` is not modified).
auto rcFile2 = rcFile;
assert(rcFile.refCountedStore.refCount == 2);
assert(File.nDestroyed == 1);
}
- // The separate `File` object is deleted when the last `RefCounted!File` handle is destroyed
+ // The separate `File` object is deleted when the last `SafeRefCounted!File` handle is destroyed
// (i.e. at the closing brace of the compound statement above, which destroys both handles: `rcFile` and `rcFile2`)
// (=> `File.nDestroyed` is incremented again, from 1 to 2):
assert(File.nDestroyed == 2);
@@ -9783,3 +10111,269 @@ unittest
Nullable!S s2 = s1;
assert(s2.get().b == 3);
}
+
+/// The old version of $(LREF SafeRefCounted), before $(LREF borrow) existed.
+/// Old code may be relying on `@safe`ty of some of the member functions which
+/// cannot be safe in the new scheme, and
+/// can avoid breakage by continuing to use this. `SafeRefCounted` should be
+/// preferred, as this type is outdated and unrecommended for new code.
+struct RefCounted(T, RefCountedAutoInitialize autoInit =
+ RefCountedAutoInitialize.yes)
+{
+ version (D_BetterC)
+ {
+ private enum enableGCScan = false;
+ }
+ else
+ {
+ private enum enableGCScan = hasIndirections!T;
+ }
+
+ // TODO remove pure when https://issues.dlang.org/show_bug.cgi?id=15862 has been fixed
+ extern(C) private pure nothrow @nogc static
+ {
+ pragma(mangle, "free") void pureFree( void *ptr );
+ static if (enableGCScan)
+ {
+ pragma(mangle, "gc_addRange") void pureGcAddRange( in void* p, size_t sz, const TypeInfo ti = null );
+ pragma(mangle, "gc_removeRange") void pureGcRemoveRange( in void* p );
+ }
+ }
+
+ struct RefCountedStore
+ {
+ private struct Impl
+ {
+ T _payload;
+ size_t _count;
+ }
+
+ private Impl* _store;
+
+ private void initialize(A...)(auto ref A args)
+ {
+ import core.lifetime : emplace, forward;
+
+ allocateStore();
+ version (D_Exceptions) scope(failure) deallocateStore();
+ emplace(&_store._payload, forward!args);
+ _store._count = 1;
+ }
+
+ private void move(ref T source) nothrow pure
+ {
+ import std.algorithm.mutation : moveEmplace;
+
+ allocateStore();
+ moveEmplace(source, _store._payload);
+ _store._count = 1;
+ }
+
+ // 'nothrow': can only generate an Error
+ private void allocateStore() nothrow pure
+ {
+ static if (enableGCScan)
+ {
+ import std.internal.memory : enforceCalloc;
+ _store = cast(Impl*) enforceCalloc(1, Impl.sizeof);
+ pureGcAddRange(&_store._payload, T.sizeof);
+ }
+ else
+ {
+ import std.internal.memory : enforceMalloc;
+ _store = cast(Impl*) enforceMalloc(Impl.sizeof);
+ }
+ }
+
+ private void deallocateStore() nothrow pure
+ {
+ static if (enableGCScan)
+ {
+ pureGcRemoveRange(&this._store._payload);
+ }
+ pureFree(_store);
+ _store = null;
+ }
+
+ @property nothrow @safe pure @nogc
+ bool isInitialized() const
+ {
+ return _store !is null;
+ }
+
+ @property nothrow @safe pure @nogc
+ size_t refCount() const
+ {
+ return isInitialized ? _store._count : 0;
+ }
+
+ void ensureInitialized()()
+ {
+ // By checking for `@disable this()` and failing early we can
+ // produce a clearer error message.
+ static assert(__traits(compiles, { static T t; }),
+ "Cannot automatically initialize `" ~ fullyQualifiedName!T ~
+ "` because `" ~ fullyQualifiedName!T ~
+ ".this()` is annotated with `@disable`.");
+ if (!isInitialized) initialize();
+ }
+
+ }
+ RefCountedStore _refCounted;
+
+ @property nothrow @safe
+ ref inout(RefCountedStore) refCountedStore() inout
+ {
+ return _refCounted;
+ }
+
+ this(A...)(auto ref A args) if (A.length > 0)
+ out
+ {
+ assert(refCountedStore.isInitialized);
+ }
+ do
+ {
+ import core.lifetime : forward;
+ _refCounted.initialize(forward!args);
+ }
+
+ this(T val)
+ {
+ _refCounted.move(val);
+ }
+
+ this(this) @safe pure nothrow @nogc
+ {
+ if (!_refCounted.isInitialized) return;
+ ++_refCounted._store._count;
+ }
+
+ ~this()
+ {
+ if (!_refCounted.isInitialized) return;
+ assert(_refCounted._store._count > 0);
+ if (--_refCounted._store._count)
+ return;
+ // Done, destroy and deallocate
+ .destroy(_refCounted._store._payload);
+ _refCounted.deallocateStore();
+ }
+
+ void opAssign(typeof(this) rhs)
+ {
+ import std.algorithm.mutation : swap;
+
+ swap(_refCounted._store, rhs._refCounted._store);
+ }
+
+ void opAssign(T rhs)
+ {
+ import std.algorithm.mutation : move;
+
+ static if (autoInit == RefCountedAutoInitialize.yes)
+ {
+ _refCounted.ensureInitialized();
+ }
+ else
+ {
+ assert(_refCounted.isInitialized);
+ }
+ move(rhs, _refCounted._store._payload);
+ }
+
+ static if (autoInit == RefCountedAutoInitialize.yes)
+ {
+ //Can't use inout here because of potential mutation
+ @property
+ ref T refCountedPayload() return
+ {
+ _refCounted.ensureInitialized();
+ return _refCounted._store._payload;
+ }
+ }
+
+ @property nothrow @safe pure @nogc
+ ref inout(T) refCountedPayload() inout return
+ {
+ assert(_refCounted.isInitialized, "Attempted to access an uninitialized payload.");
+ return _refCounted._store._payload;
+ }
+
+ alias refCountedPayload this;
+
+ static if (is(T == struct) && !is(typeof((ref T t) => t.toString())))
+ {
+ string toString(this This)()
+ {
+ import std.conv : to;
+
+ static if (autoInit)
+ return to!string(refCountedPayload);
+ else
+ {
+ if (!_refCounted.isInitialized)
+ return This.stringof ~ "(RefCountedStore(null))";
+ else
+ return to!string(_refCounted._store._payload);
+ }
+ }
+ }
+}
+
+///
+@betterC pure @system nothrow @nogc unittest
+{
+ auto rc1 = RefCounted!int(5);
+ assert(rc1 == 5);
+ auto rc2 = rc1;
+ rc2 = 42;
+ assert(rc1 == 42);
+}
+
+// More unit tests below SafeRefCounted
+
+/**
+ * Like $(LREF safeRefCounted) but used to initialize $(LREF RefCounted)
+ * instead. Intended for backwards compatibility, otherwise it is preferable
+ * to use `safeRefCounted`.
+ */
+RefCounted!(T, RefCountedAutoInitialize.no) refCounted(T)(T val)
+{
+ typeof(return) res;
+ res._refCounted.move(val);
+ return res;
+}
+
+///
+@system unittest
+{
+ static struct File
+ {
+ static size_t nDestroyed;
+ string name;
+ @disable this(this); // not copyable
+ ~this() { name = null; ++nDestroyed; }
+ }
+
+ auto file = File("name");
+ assert(file.name == "name");
+ static assert(!__traits(compiles, {auto file2 = file;}));
+ assert(File.nDestroyed == 0);
+
+ {
+ import std.algorithm.mutation : move;
+ auto rcFile = refCounted(move(file));
+ assert(rcFile.name == "name");
+ assert(File.nDestroyed == 1);
+ assert(file.name == null);
+
+ auto rcFile2 = rcFile;
+ assert(rcFile.refCountedStore.refCount == 2);
+ assert(File.nDestroyed == 1);
+ }
+
+ assert(File.nDestroyed == 2);
+}
+
+// More unit tests below safeRefCounted
diff --git a/std/uni/package.d b/std/uni/package.d
index e12a70cfe80..8a032aaa514 100644
--- a/std/uni/package.d
+++ b/std/uni/package.d
@@ -426,7 +426,7 @@ $(TR $(TD Building blocks) $(TD
$(SECTION Construction of lookup tables)
$(P The Unicode standard describes a set of algorithms that
depend on having the ability to quickly look up various properties
- of a code point. Given the the codespace of about 1 million $(CODEPOINTS),
+ of a code point. Given the codespace of about 1 million $(CODEPOINTS),
it is not a trivial task to provide a space-efficient solution for
the multitude of properties.
)
diff --git a/std/utf.d b/std/utf.d
index d22dac8b15d..8d94e121ff2 100644
--- a/std/utf.d
+++ b/std/utf.d
@@ -3571,7 +3571,7 @@ enum dchar replacementDchar = '\uFFFD';
* of characters (including strings) or a type that implicitly converts to a string type.
* Returns:
* If `r` is not an auto-decodable string (i.e. a narrow string or a
- * user-defined type that implicits converts to a string type), then `r`
+ * user-defined type that implicitly converts to a string type), then `r`
* is returned.
*
* Otherwise, `r` is converted to its corresponding string type (if it's
diff --git a/std/xml.d b/std/xml.d
deleted file mode 100644
index fdfdc3f70ab..00000000000
--- a/std/xml.d
+++ /dev/null
@@ -1,3113 +0,0 @@
-// Written in the D programming language.
-
-/**
-$(RED Warning: This module is considered out-dated and not up to Phobos'
- current standards. It will be removed from Phobos in 2.101.0.
- If you still need it, go to $(LINK https://github.com/DigitalMars/undeaD))
- */
-
-/*
-Classes and functions for creating and parsing XML
-
-The basic architecture of this module is that there are standalone functions,
-classes for constructing an XML document from scratch (Tag, Element and
-Document), and also classes for parsing a pre-existing XML file (ElementParser
-and DocumentParser). The parsing classes may be used to build a
-Document, but that is not their primary purpose. The handling capabilities of
-DocumentParser and ElementParser are sufficiently customizable that you can
-make them do pretty much whatever you want.
-
-Example: This example creates a DOM (Document Object Model) tree
- from an XML file.
-------------------------------------------------------------------------------
-import std.xml;
-import std.stdio;
-import std.string;
-import std.file;
-
-// books.xml is used in various samples throughout the Microsoft XML Core
-// Services (MSXML) SDK.
-//
-// See http://msdn2.microsoft.com/en-us/library/ms762271(VS.85).aspx
-
-void main()
-{
- string s = cast(string) std.file.read("books.xml");
-
- // Check for well-formedness
- check(s);
-
- // Make a DOM tree
- auto doc = new Document(s);
-
- // Plain-print it
- writeln(doc);
-}
-------------------------------------------------------------------------------
-
-Example: This example does much the same thing, except that the file is
- deconstructed and reconstructed by hand. This is more work, but the
- techniques involved offer vastly more power.
-------------------------------------------------------------------------------
-import std.xml;
-import std.stdio;
-import std.string;
-
-struct Book
-{
- string id;
- string author;
- string title;
- string genre;
- string price;
- string pubDate;
- string description;
-}
-
-void main()
-{
- string s = cast(string) std.file.read("books.xml");
-
- // Check for well-formedness
- check(s);
-
- // Take it apart
- Book[] books;
-
- auto xml = new DocumentParser(s);
- xml.onStartTag["book"] = (ElementParser xml)
- {
- Book book;
- book.id = xml.tag.attr["id"];
-
- xml.onEndTag["author"] = (in Element e) { book.author = e.text(); };
- xml.onEndTag["title"] = (in Element e) { book.title = e.text(); };
- xml.onEndTag["genre"] = (in Element e) { book.genre = e.text(); };
- xml.onEndTag["price"] = (in Element e) { book.price = e.text(); };
- xml.onEndTag["publish-date"] = (in Element e) { book.pubDate = e.text(); };
- xml.onEndTag["description"] = (in Element e) { book.description = e.text(); };
-
- xml.parse();
-
- books ~= book;
- };
- xml.parse();
-
- // Put it back together again;
- auto doc = new Document(new Tag("catalog"));
- foreach (book;books)
- {
- auto element = new Element("book");
- element.tag.attr["id"] = book.id;
-
- element ~= new Element("author", book.author);
- element ~= new Element("title", book.title);
- element ~= new Element("genre", book.genre);
- element ~= new Element("price", book.price);
- element ~= new Element("publish-date",book.pubDate);
- element ~= new Element("description", book.description);
-
- doc ~= element;
- }
-
- // Pretty-print it
- writefln(join(doc.pretty(3),"\n"));
-}
--------------------------------------------------------------------------------
-Copyright: Copyright Janice Caron 2008 - 2009.
-License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
-Authors: Janice Caron
-Source: $(PHOBOSSRC std/xml.d)
-*/
-/*
- Copyright Janice Caron 2008 - 2009.
-Distributed under the Boost Software License, Version 1.0.
- (See accompanying file LICENSE_1_0.txt or copy at
- http://www.boost.org/LICENSE_1_0.txt)
-*/
-deprecated("Will be removed from Phobos in 2.101.0. If you still need it, go to https://github.com/DigitalMars/undeaD")
-module std.xml;
-
-enum cdata = "= 0x20)
- return true;
- switch (c)
- {
- case 0xA:
- case 0x9:
- case 0xD:
- return true;
- default:
- return false;
- }
- }
- else if (0xE000 <= c && c <= 0x10FFFF)
- {
- if ((c & 0x1FFFFE) != 0xFFFE) // U+FFFE and U+FFFF
- return true;
- }
- return false;
-}
-
-@safe @nogc nothrow pure unittest
-{
- assert(!isChar(cast(dchar) 0x8));
- assert( isChar(cast(dchar) 0x9));
- assert( isChar(cast(dchar) 0xA));
- assert(!isChar(cast(dchar) 0xB));
- assert(!isChar(cast(dchar) 0xC));
- assert( isChar(cast(dchar) 0xD));
- assert(!isChar(cast(dchar) 0xE));
- assert(!isChar(cast(dchar) 0x1F));
- assert( isChar(cast(dchar) 0x20));
- assert( isChar('J'));
- assert( isChar(cast(dchar) 0xD7FF));
- assert(!isChar(cast(dchar) 0xD800));
- assert(!isChar(cast(dchar) 0xDFFF));
- assert( isChar(cast(dchar) 0xE000));
- assert( isChar(cast(dchar) 0xFFFD));
- assert(!isChar(cast(dchar) 0xFFFE));
- assert(!isChar(cast(dchar) 0xFFFF));
- assert( isChar(cast(dchar) 0x10000));
- assert( isChar(cast(dchar) 0x10FFFF));
- assert(!isChar(cast(dchar) 0x110000));
-
- debug (stdxml_TestHardcodedChecks)
- {
- foreach (c; 0 .. dchar.max + 1)
- assert(isChar(c) == lookup(CharTable, c));
- }
-}
-
-/*
- * Returns true if the character is whitespace according to the XML standard
- *
- * Only the following characters are considered whitespace in XML - space, tab,
- * carriage return and linefeed
- *
- * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
- *
- * Params:
- * c = the character to be tested
- */
-bool isSpace(dchar c) @safe @nogc pure nothrow
-{
- return c == '\u0020' || c == '\u0009' || c == '\u000A' || c == '\u000D';
-}
-
-/*
- * Returns true if the character is a digit according to the XML standard
- *
- * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
- *
- * Params:
- * c = the character to be tested
- */
-bool isDigit(dchar c) @safe @nogc pure nothrow
-{
- if (c <= 0x0039 && c >= 0x0030)
- return true;
- else
- return lookup(DigitTable,c);
-}
-
-@safe @nogc nothrow pure unittest
-{
- debug (stdxml_TestHardcodedChecks)
- {
- foreach (c; 0 .. dchar.max + 1)
- assert(isDigit(c) == lookup(DigitTable, c));
- }
-}
-
-/*
- * Returns true if the character is a letter according to the XML standard
- *
- * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
- *
- * Params:
- * c = the character to be tested
- */
-bool isLetter(dchar c) @safe @nogc nothrow pure // rule 84
-{
- return isIdeographic(c) || isBaseChar(c);
-}
-
-/*
- * Returns true if the character is an ideographic character according to the
- * XML standard
- *
- * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
- *
- * Params:
- * c = the character to be tested
- */
-bool isIdeographic(dchar c) @safe @nogc nothrow pure
-{
- if (c == 0x3007)
- return true;
- if (c <= 0x3029 && c >= 0x3021 )
- return true;
- if (c <= 0x9FA5 && c >= 0x4E00)
- return true;
- return false;
-}
-
-@safe @nogc nothrow pure unittest
-{
- assert(isIdeographic('\u4E00'));
- assert(isIdeographic('\u9FA5'));
- assert(isIdeographic('\u3007'));
- assert(isIdeographic('\u3021'));
- assert(isIdeographic('\u3029'));
-
- debug (stdxml_TestHardcodedChecks)
- {
- foreach (c; 0 .. dchar.max + 1)
- assert(isIdeographic(c) == lookup(IdeographicTable, c));
- }
-}
-
-/*
- * Returns true if the character is a base character according to the XML
- * standard
- *
- * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
- *
- * Params:
- * c = the character to be tested
- */
-bool isBaseChar(dchar c) @safe @nogc nothrow pure
-{
- return lookup(BaseCharTable,c);
-}
-
-/*
- * Returns true if the character is a combining character according to the
- * XML standard
- *
- * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
- *
- * Params:
- * c = the character to be tested
- */
-bool isCombiningChar(dchar c) @safe @nogc nothrow pure
-{
- return lookup(CombiningCharTable,c);
-}
-
-/*
- * Returns true if the character is an extender according to the XML standard
- *
- * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
- *
- * Params:
- * c = the character to be tested
- */
-bool isExtender(dchar c) @safe @nogc nothrow pure
-{
- return lookup(ExtenderTable,c);
-}
-
-/*
- * Encodes a string by replacing all characters which need to be escaped with
- * appropriate predefined XML entities.
- *
- * encode() escapes certain characters (ampersand, quote, apostrophe, less-than
- * and greater-than), and similarly, decode() unescapes them. These functions
- * are provided for convenience only. You do not need to use them when using
- * the std.xml classes, because then all the encoding and decoding will be done
- * for you automatically.
- *
- * If the string is not modified, the original will be returned.
- *
- * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
- *
- * Params:
- * s = The string to be encoded
- *
- * Returns: The encoded string
- *
- * Example:
- * --------------
- * writefln(encode("a > b")); // writes "a > b"
- * --------------
- */
-S encode(S)(S s)
-{
- import std.array : appender;
-
- string r;
- size_t lastI;
- auto result = appender!S();
-
- foreach (i, c; s)
- {
- switch (c)
- {
- case '&': r = "&"; break;
- case '"': r = """; break;
- case '\'': r = "'"; break;
- case '<': r = "<"; break;
- case '>': r = ">"; break;
- default: continue;
- }
- // Replace with r
- result.put(s[lastI .. i]);
- result.put(r);
- lastI = i + 1;
- }
-
- if (!result.data.ptr) return s;
- result.put(s[lastI .. $]);
- return result.data;
-}
-
-@safe pure unittest
-{
- auto s = "hello";
- assert(encode(s) is s);
- assert(encode("a > b") == "a > b", encode("a > b"));
- assert(encode("a < b") == "a < b");
- assert(encode("don't") == "don't");
- assert(encode("\"hi\"") == ""hi"", encode("\"hi\""));
- assert(encode("cat & dog") == "cat & dog");
-}
-
-/*
- * Mode to use for decoding.
- *
- * $(DDOC_ENUM_MEMBERS NONE) Do not decode
- * $(DDOC_ENUM_MEMBERS LOOSE) Decode, but ignore errors
- * $(DDOC_ENUM_MEMBERS STRICT) Decode, and throw exception on error
- */
-enum DecodeMode
-{
- NONE, LOOSE, STRICT
-}
-
-/*
- * Decodes a string by unescaping all predefined XML entities.
- *
- * encode() escapes certain characters (ampersand, quote, apostrophe, less-than
- * and greater-than), and similarly, decode() unescapes them. These functions
- * are provided for convenience only. You do not need to use them when using
- * the std.xml classes, because then all the encoding and decoding will be done
- * for you automatically.
- *
- * This function decodes the entities &, ", ',
- * < and >,
- * as well as decimal and hexadecimal entities such as €
- *
- * If the string does not contain an ampersand, the original will be returned.
- *
- * Note that the "mode" parameter can be one of DecodeMode.NONE (do not
- * decode), DecodeMode.LOOSE (decode, but ignore errors), or DecodeMode.STRICT
- * (decode, and throw a DecodeException in the event of an error).
- *
- * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
- *
- * Params:
- * s = The string to be decoded
- * mode = (optional) Mode to use for decoding. (Defaults to LOOSE).
- *
- * Throws: DecodeException if mode == DecodeMode.STRICT and decode fails
- *
- * Returns: The decoded string
- *
- * Example:
- * --------------
- * writefln(decode("a > b")); // writes "a > b"
- * --------------
- */
-string decode(string s, DecodeMode mode=DecodeMode.LOOSE) @safe pure
-{
- import std.algorithm.searching : startsWith;
-
- if (mode == DecodeMode.NONE) return s;
-
- string buffer;
- foreach (ref i; 0 .. s.length)
- {
- char c = s[i];
- if (c != '&')
- {
- if (buffer.length != 0) buffer ~= c;
- }
- else
- {
- if (buffer.length == 0)
- {
- buffer = s[0 .. i].dup;
- }
- if (startsWith(s[i..$],""))
- {
- try
- {
- dchar d;
- string t = s[i..$];
- checkCharRef(t, d);
- char[4] temp;
- import std.utf : encode;
- buffer ~= temp[0 .. encode(temp, d)];
- i = s.length - t.length - 1;
- }
- catch (Err e)
- {
- if (mode == DecodeMode.STRICT)
- throw new DecodeException("Unescaped &");
- buffer ~= '&';
- }
- }
- else if (startsWith(s[i..$],"&" )) { buffer ~= '&'; i += 4; }
- else if (startsWith(s[i..$],""")) { buffer ~= '"'; i += 5; }
- else if (startsWith(s[i..$],"'")) { buffer ~= '\''; i += 5; }
- else if (startsWith(s[i..$],"<" )) { buffer ~= '<'; i += 3; }
- else if (startsWith(s[i..$],">" )) { buffer ~= '>'; i += 3; }
- else
- {
- if (mode == DecodeMode.STRICT)
- throw new DecodeException("Unescaped &");
- buffer ~= '&';
- }
- }
- }
- return (buffer.length == 0) ? s : buffer;
-}
-
-@safe pure unittest
-{
- void assertNot(string s) pure
- {
- bool b = false;
- try { decode(s,DecodeMode.STRICT); }
- catch (DecodeException e) { b = true; }
- assert(b,s);
- }
-
- // Assert that things that should work, do
- auto s = "hello";
- assert(decode(s, DecodeMode.STRICT) is s);
- assert(decode("a > b", DecodeMode.STRICT) == "a > b");
- assert(decode("a < b", DecodeMode.STRICT) == "a < b");
- assert(decode("don't", DecodeMode.STRICT) == "don't");
- assert(decode(""hi"", DecodeMode.STRICT) == "\"hi\"");
- assert(decode("cat & dog", DecodeMode.STRICT) == "cat & dog");
- assert(decode("*", DecodeMode.STRICT) == "*");
- assert(decode("*", DecodeMode.STRICT) == "*");
- assert(decode("cat & dog", DecodeMode.LOOSE) == "cat & dog");
- assert(decode("a > b", DecodeMode.LOOSE) == "a > b");
- assert(decode("", DecodeMode.LOOSE) == "");
- assert(decode("", DecodeMode.LOOSE) == "");
- assert(decode("G;", DecodeMode.LOOSE) == "G;");
- assert(decode("G;", DecodeMode.LOOSE) == "G;");
-
- // Assert that things that shouldn't work, don't
- assertNot("cat & dog");
- assertNot("a > b");
- assertNot("");
- assertNot("");
- assertNot("G;");
- assertNot("G;");
-}
-
-/*
- * Class representing an XML document.
- *
- * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0)
- *
- */
-class Document : Element
-{
- /*
- * Contains all text which occurs before the root element.
- * Defaults to <?xml version="1.0"?>
- */
- string prolog = "";
- /*
- * Contains all text which occurs after the root element.
- * Defaults to the empty string
- */
- string epilog;
-
- /*
- * Constructs a Document by parsing XML text.
- *
- * This function creates a complete DOM (Document Object Model) tree.
- *
- * The input to this function MUST be valid XML.
- * This is enforced by DocumentParser's in contract.
- *
- * Params:
- * s = the complete XML text.
- */
- this(string s)
- in
- {
- assert(s.length != 0);
- }
- do
- {
- auto xml = new DocumentParser(s);
- string tagString = xml.tag.tagString;
-
- this(xml.tag);
- prolog = s[0 .. tagString.ptr - s.ptr];
- parse(xml);
- epilog = *xml.s;
- }
-
- /*
- * Constructs a Document from a Tag.
- *
- * Params:
- * tag = the start tag of the document.
- */
- this(const(Tag) tag)
- {
- super(tag);
- }
-
- const
- {
- /*
- * Compares two Documents for equality
- *
- * Example:
- * --------------
- * Document d1,d2;
- * if (d1 == d2) { }
- * --------------
- */
- override bool opEquals(scope const Object o) const
- {
- const doc = toType!(const Document)(o);
- return prolog == doc.prolog
- && (cast(const) this).Element.opEquals(cast(const) doc)
- && epilog == doc.epilog;
- }
-
- /*
- * Compares two Documents
- *
- * You should rarely need to call this function. It exists so that
- * Documents can be used as associative array keys.
- *
- * Example:
- * --------------
- * Document d1,d2;
- * if (d1 < d2) { }
- * --------------
- */
- override int opCmp(scope const Object o) scope const
- {
- const doc = toType!(const Document)(o);
- if (prolog != doc.prolog)
- return prolog < doc.prolog ? -1 : 1;
- if (int cmp = this.Element.opCmp(doc))
- return cmp;
- if (epilog != doc.epilog)
- return epilog < doc.epilog ? -1 : 1;
- return 0;
- }
-
- /*
- * Returns the hash of a Document
- *
- * You should rarely need to call this function. It exists so that
- * Documents can be used as associative array keys.
- */
- override size_t toHash() scope const @trusted
- {
- return hash(prolog, hash(epilog, (cast() this).Element.toHash()));
- }
-
- /*
- * Returns the string representation of a Document. (That is, the
- * complete XML of a document).
- */
- override string toString() scope const @safe
- {
- return prolog ~ super.toString() ~ epilog;
- }
- }
-}
-
-@system unittest
-{
- // https://issues.dlang.org/show_bug.cgi?id=14966
- auto xml = `