diff --git a/.gitignore b/.gitignore index 6419010722..0b30bb3735 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,9 @@ modlist-release.ddoc /d.tag /.generated + +# DUB binaries +/assert_writeln_magic + +# Generated changelogs +changelog/*_pre.dd diff --git a/README.md b/README.md index 2410a6ef19..1c506defc4 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,4 @@ documentation, and some articles. * [Wiki](https://wiki.dlang.org/) If you wish to contribute to the website or language documentation, please see -the [CONTRIBUTING.md file] (CONTRIBUTING.md). +the [CONTRIBUTING.md file](CONTRIBUTING.md) and [wiki entry](https://wiki.dlang.org/Contributing_to_dlang.org). diff --git a/assert_writeln_magic.d b/assert_writeln_magic.d new file mode 100755 index 0000000000..b770c113ff --- /dev/null +++ b/assert_writeln_magic.d @@ -0,0 +1,323 @@ +#!/usr/bin/env dub +/++ +dub.sdl: +dependency "libdparse" version="0.7.0-beta.7" +name "assert_writeln_magic" ++/ +/* + * Tries to convert `assert`'s into user-friendly `writeln` calls. + * The objective of this tool is to be conservative as + * broken example look a lot worse than a few statements + * that could have potentially been rewritten. + * + * - only EqualExpressions are "lowered" + * - static asserts are ignored + * - only single-line assers are rewritten + * + * Copyright (C) 2017 by D Language Foundation + * + * Author: Sebastian Wilzbach + * + * 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) +*/ +// Written in the D programming language. + +import dparse.ast; +import std.algorithm; +import std.conv; +import std.experimental.logger; +import std.range; +import std.stdio; +import std.typecons; + +private string formatNode(T)(const T t) +{ + import dparse.formatter; + import std.array : appender; + + auto writer = appender!string(); + auto formatter = new Formatter!(typeof(writer))(writer); + formatter.format(t); + return writer.data; +} + +class TestVisitor : ASTVisitor +{ + import dparse.lexer : tok, Token; + + this(string fileName, string destFile) + { + this.fileName = fileName; + fl = FileLines(fileName, destFile); + } + + alias visit = ASTVisitor.visit; + + override void visit(const Unittest test) + { + resetTestState(); + inTest = true; + scope(exit) inTest = false; + test.accept(this); + + processLastAssert(); + } + + override void visit(const EqualExpression expr) + { + enum eqToken = tok!"=="; + if (inAssert && expr.operator == eqToken && expr.left !is null && expr.right !is null) + lastEqualExpression = expr; + } + + override void visit(const AssertExpression expr) + { + if (inFunctionCall) + return; + + lastAssert = expr; + inAssert = true; + expr.accept(this); + inAssert = false; + fromAssert = true; + } + + // for now static asserts are ignored + override void visit(const StaticAssertStatement expr) + { + fromStaticAssert = true; + expr.accept(this); + } + + /** + The following code (in std.concurrency) leads to false positives: + + assertNotThrown!AssertError(assert(receiveOnly!int() == i)); + + Hence we simply ignore all asserts in function calls. + */ + override void visit(const FunctionCallExpression expr) + { + inFunctionCall = true; + expr.accept(this); + inFunctionCall = false; + } + + /// A single line + override void visit(const DeclarationOrStatement expr) + { + processLastAssert(); + expr.accept(this); + } + + void processLastAssert() + { + import std.uni : isWhite; + import std.format : format; + + if (fromAssert && !fromStaticAssert && + lastEqualExpression !is null && lastAssert !is null) + { + auto e = lastEqualExpression; + if (e.left !is null && e.right !is null) + { + // libdparse starts the line count with 1 + auto lineNr = lastAssert.line - 1; + + // only replace single-line expressions (for now) + if (fl[lineNr].endsWith(";")) + { + auto wsLen = fl[lineNr].countUntil!(u => !u.isWhite); + auto indent = fl[lineNr][0 .. wsLen]; + + if (fl[lineNr][wsLen .. $].startsWith("assert", "static assert")) + { + auto left = lastEqualExpression.left.formatNode; + auto right = lastEqualExpression.right.formatNode; + + if (left.length + right.length > 80) + fl[lineNr] = format("%s// %s\n%swriteln(%s);", indent, right, indent, left); + else + fl[lineNr] = format("%swriteln(%s); // %s", indent, left, right); + + //writefln("line: %d, column: %d", lastAssert.line, lastAssert.column); + } + } + } + } + resetTestState(); + } + +private: + + void resetTestState() + { + fromAssert = false; + fromStaticAssert = false; + lastEqualExpression = null; + lastAssert = null; + } + + /// within in the node + bool inTest; + bool inAssert; + bool inFunctionCall; + + /// at a sibling after the node was seen, but the upper parent hasn't been reached yet + bool fromAssert; + bool fromStaticAssert; + + Rebindable!(const AssertExpression) lastAssert; + Rebindable!(const EqualExpression) lastEqualExpression; + + string fileName; + FileLines fl; +} + +void parseFile(string fileName, string destFile) +{ + import dparse.lexer; + import dparse.parser : parseModule; + import dparse.rollback_allocator : RollbackAllocator; + import std.array : uninitializedArray; + + auto inFile = File(fileName); + if (inFile.size == 0) + warningf("%s is empty", inFile.name); + + ubyte[] sourceCode = uninitializedArray!(ubyte[])(to!size_t(inFile.size)); + inFile.rawRead(sourceCode); + LexerConfig config; + auto cache = StringCache(StringCache.defaultBucketCount); + const(Token)[] tokens = getTokensForParser(sourceCode, config, &cache).array; + + RollbackAllocator rba; + auto m = parseModule(tokens, fileName, &rba); + auto visitor = new TestVisitor(fileName, destFile); + visitor.visit(m); + delete visitor; +} + +// Modify a path under oldBase to a new path with the same subpath under newBase. +// E.g.: `/foo/bar`.rebasePath(`/foo`, `/quux`) == `/quux/bar` +string rebasePath(string path, string oldBase, string newBase) +{ + import std.path : absolutePath, buildPath, relativePath; + return buildPath(newBase, path.absolutePath.relativePath(oldBase.absolutePath)); +} + +void main(string[] args) +{ + import std.file; + import std.getopt; + import std.path; + + string inputDir, outputDir; + string[] ignoredFiles; + + auto helpInfo = getopt(args, config.required, + "inputdir|i", "Folder to start the recursive search for unittest blocks (can be a single file)", &inputDir, + "outputdir|o", "Alternative folder to use as output (can be a single file)", &outputDir, + "ignore", "List of files to exclude (partial matching is supported)", &ignoredFiles); + + if (helpInfo.helpWanted) + { + return defaultGetoptPrinter(`assert_writeln_magic +Tries to lower EqualExpression in AssertExpressions of Unittest blocks to commented writeln calls. +`, helpInfo.options); + } + + inputDir = inputDir.asNormalizedPath.array; + + DirEntry[] files; + + // inputDir as default output directory + if (!outputDir.length) + outputDir = inputDir; + + if (inputDir.isFile) + { + files = [DirEntry(inputDir)]; + inputDir = ""; + } + else + { + files = dirEntries(inputDir, SpanMode.depth).filter!( + a => a.name.endsWith(".d") && !a.name.canFind(".git")).array; + } + + foreach (file; files) + { + if (!ignoredFiles.any!(x => file.name.canFind(x))) + { + // single files + if (inputDir.length == 0) + parseFile(file.name, outputDir); + else + parseFile(file.name, file.name.rebasePath(inputDir, outputDir)); + } + } +} + +/** +A simple line-based in-memory representation of a file. + - will automatically write all changes when the object is destructed + - will use a temporary file to do safe, whole file swaps +*/ +struct FileLines +{ + import std.array, std.file, std.path; + + string[] lines; + string destFile; + bool overwriteInputFile; + bool hasWrittenChanges; + + this(string inputFile, string destFile) + { + stderr.writefln("%s -> %s", inputFile, destFile); + this.overwriteInputFile = inputFile == destFile; + this.destFile = destFile; + lines = File(inputFile).byLineCopy.array; + + destFile.dirName.mkdirRecurse; + } + + // dumps all changes + ~this() + { + if (overwriteInputFile) + { + if (hasWrittenChanges) + { + auto tmpFile = File(destFile ~ ".tmp", "w"); + writeLinesToFile(tmpFile); + tmpFile.close; + tmpFile.name.rename(destFile); + } + } + else + { + writeLinesToFile(File(destFile, "w")); + } + } + + // writes all changes to a random, temporary file + void writeLinesToFile(File outFile) { + // dump file + foreach (line; lines) + outFile.writeln(line); + // within the docs we automatically inject std.stdio (hence we need to do the same here) + // writeln needs to be @nogc, @safe, pure and nothrow (we just fake it) + outFile.writeln("// \nprivate void writeln(T)(T l) { }"); + outFile.flush; + } + + string opIndex(size_t i) { return lines[i]; } + void opIndexAssign(string line, size_t i) { + hasWrittenChanges = true; + lines[i] = line; + } +} diff --git a/changelog/2.073.0.dd b/changelog/2.073.0.dd index d16ada0114..a6acd42dda 100644 --- a/changelog/2.073.0.dd +++ b/changelog/2.073.0.dd @@ -84,10 +84,10 @@ $(LI $(LNAME2 std-algorithm-searching-maxindex,Added `std.algorithm.searching.ma ------- import std.algorithm.searching : maxIndex; int[] a = [5, 4, 2, 1, 9, 10]; - assert(a.minIndex == 5); + assert(a.maxIndex == 5); int[] a; - assert(a.minIndex == -1); + assert(a.maxIndex == -1); ------- ) diff --git a/changelog/2.073.2.dd b/changelog/2.073.2.dd index 587d04a368..cdd135cf54 100644 --- a/changelog/2.073.2.dd +++ b/changelog/2.073.2.dd @@ -29,5 +29,5 @@ $(LI $(BUGZILLA 17198): rdmd does not recompile when --extra-file is added) ) $(CHANGELOG_NAV_LAST 2.073.1) Macros: - VER=LATEST + VER=2.073.2 TITLE=Change Log: $(VER) diff --git a/changelog/changelog.ddoc b/changelog/changelog.ddoc index a452b0153f..41b96aaf62 100644 --- a/changelog/changelog.ddoc +++ b/changelog/changelog.ddoc @@ -25,6 +25,15 @@ $(SMALL released $1, $2) $4 ) +NIGHTLY_VERSION= +$(DIVC version, +$(P +$(B $(LARGE $(LINK2 http://nightlies.dlang.org, Download D nightlies)))$(BR) +$(SMALL $1) +) +$4 +) + BUGZILLA = Bugzilla $0 CPPBUGZILLA = Bugzilla $0 DSTRESS = dstress $0 diff --git a/cpptod.dd b/cpptod.dd index 83efe76167..0bc5b3e2ef 100644 --- a/cpptod.dd +++ b/cpptod.dd @@ -45,7 +45,7 @@ class Foo $(H4 The D Way) - Constructors are defined with the this keyword: + Constructors are defined with the `this` keyword: ------ class Foo @@ -76,7 +76,7 @@ class B : A $(H4 The D Way) - The base class constructor is called with the super syntax: + The base class constructor is called with the `super` syntax: ------ class A { this() { ... } } @@ -573,24 +573,33 @@ void test() $(H4 The D Way) The D version is analogous, though a little simpler, taking - advantage of promotion of single template members to the + advantage of + $(HTTPS dlang.org/spec/template.html#implicit_template_properties, + Eponymous Templates) - promotion of single template members to the enclosing name space: - ------ template factorial(int n) { - enum { factorial = n * .factorial!(n-1) } + enum factorial = n * .factorial!(n-1); } template factorial(int n : 1) { - enum { factorial = 1 } + enum factorial = 1; } void test() { - writefln("%d", factorial!(4)); // prints 24 + writeln(factorial!(4)); // prints 24 } +------ + The template blocks can be made shorter using + $(HTTPS dlang.org/spec/template.html#variable-template, + Enum Template) syntax: +------ +enum factorial(int n) = n * .factorial!(n-1); + +enum factorial(int n : 1) = 1; ------
Running...
Running...' + '
| @@ -160,7 +160,7 @@ std:cout << "Hello World!" << std::endl; ) ) $(P - Whereas pointers were important in C, C++ embraced them as the main vehicle for the Standard Library. STL algorithms use iterators, objects that are either pointers themselves or imitate the behavior (and the pitfalls) of pointers. Just like with pointers, a programmer's error in using iterators leads to undefined behavior (see the $(CODE swap_range) example). + Whereas pointers were important in C, C++ embraced them as the main vehicle for the Standard Library. STL algorithms use iterators, objects that are either pointers themselves or imitate the behavior (and the pitfalls) of pointers. Just like with pointers, a programmer's error in using iterators leads to undefined behavior (see the $(CODE swap_ranges) example). ) $(P @@ -189,14 +189,14 @@ writeln("Hello Safe World!"); --- $(P - The function $(CODE writeln) is the equivalent of the C $(CODE printf) (more precisely, it's the representative of a family of output functions including $(CODE write) and its formatting versions, $(CODE writef) and $(CODE writefln)). Just like $(CODE printf), $(CODE writeln) accepts a variable number of arguments of arbitrary types. But here the similarity ends. As long as you pass SafeD-arguments to $(CODE writeln), you are guaranteed not to encounter any undefined behavior. Here, $(CODE writeln) is called with a single argument of the type $(CODE string). In contrast to C, D $(CODE string) is not a pointer. It is an array of $(CODE immutable char), and arrays are a built into the safe subset of D. + The function $(CODE writeln) is the equivalent of the C $(CODE printf) (more precisely, it's the representative of a family of output functions including $(CODE write) and its formatting versions, $(CODE writef) and $(CODE writefln)). Just like $(CODE printf), $(CODE writeln) accepts a variable number of arguments of arbitrary types. But here the similarity ends. As long as you pass SafeD-arguments to $(CODE writeln), you are guaranteed not to encounter any undefined behavior. Here, $(CODE writeln) is called with a single argument of the type $(CODE string). In contrast to C, a D $(CODE string) is not a pointer. It is an array of $(CODE immutable char), and arrays are built into the safe subset of D. ) $(P You might be interested to know how the safety of $(CODE writeln) is accomplished in D. One possible approach would have been to make $(CODE writeln) a compiler intrinsic, so that correct code would be generated on a case-by-case basis. The beauty of D is that it gives a sophisticated programmer tools that allow such case-by-case code generation of code. The advanced features used in the implementation of writeln are: $(UL $(LI Compile-time code generation using templates, and) - $(LI A safe mechanism for dealing with variable number of arguments using tuples.) + $(LI A safe mechanism for dealing with a variable number of arguments using tuples.) ) ) @@ -208,11 +208,11 @@ $(SECTION2 SafeD Libraries, ) $(P - A lot of advanced features of D are compatible with SafeD, as long as they don't force the user to use unsafe types. For instance, a library may provide the implementation of a generic list. The list can be instantiated with any type, in particular with a pointer type. A list of pointers, by definition, cannot be safe, because pointer arithmetic is unsound. However, a list of ints or class objects can and should be safe. That's why such ageneric lists can be used in SafeD, even though their usage outside of SafeD may be unsafe. + A lot of advanced features of D are compatible with SafeD, as long as they don't force the user to use unsafe types. For instance, a library may provide the implementation of a generic list. The list can be instantiated with any type, in particular with a pointer type. A list of pointers, by definition, cannot be safe, because pointer arithmetic is unsound. However, a list of ints or class objects can and should be safe. That's why such generic lists can be used in SafeD, even though their usage outside of SafeD may be unsafe. ) $(P - Moreover, it might be more efficient to base the internal implementation of a list on pointers. As long as these pointers are not exposed to the client, such an implementation might be certified to be SafeD compatible1 . You can have a cake (advanced features of D) and eat it too (take advantage of them in SafeD). + Moreover, it might be more efficient to base the internal implementation of a list on pointers. As long as these pointers are not exposed to the client, such an implementation might be certified to be SafeD compatible1. You can have a cake (advanced features of D) and eat it too (take advantage of them in SafeD). ) ) diff --git a/spec/hash-map.dd b/spec/hash-map.dd index f27753df00..2facff429f 100644 --- a/spec/hash-map.dd +++ b/spec/hash-map.dd @@ -341,7 +341,11 @@ Properties for associative arrays are: ForeachStatement) which will iterate over key-value pairs of the associative array. The returned pairs are represented by an opaque type with $(D .key) and $(D .value) properties for accessing the key and - value of the pair, respectively.)) + value of the pair, respectively. Note that this is a low-level + interface to iterating over the associative array and is not compatible + with the $(LINK2 $(ROOT_DIR)phobos/std_typecons.html#.Tuple,`Tuple`) + type in Phobos. For compatibility with `Tuple`, use + $(LINK2 $(ROOT_DIR)phobos/std_array.html#.byPair,std.array.byPair) instead.)) $(TROW $(D .get(Key key, lazy Value defVal)), $(ARGS Looks up $(D key); if it exists returns corresponding value else evaluates and returns $(D defVal).)) diff --git a/spec/module.dd b/spec/module.dd index 547cb3469e..3efab02bc1 100644 --- a/spec/module.dd +++ b/spec/module.dd @@ -549,18 +549,19 @@ $(H3 $(LNAME2 override_cycle_abort, Overriding Cycle Detection Abort)) ) $(OL - $(LI `deprecate` The default behavior. This functions just like `abort`, - but will use the pre-2.072 algorithm to determine if the cycle was - undetected. If so, the old algorithm is used, but a deprecation - message is printed. After 2.073, the `abort` option will be the default.) - $(LI `abort` The normal behavior as described in the previous section. - After 2.073, this will be the default behavior.) + $(LI `abort` The default behavior. The normal behavior as described + in the previous section) + $(LI `deprecate` This functions just like `abort`, but upon cycle + detection the runtime will use a flawed pre-2.072 algorithm to + determine if the cycle was previously detected. If no cycles are + detected in the old algorithm, execution continues, but a + deprecation message is printed.) $(LI `print` Print all cycles detected, but do not abort execution. - Order of static construction is implementation defined, and not - guaranteed to be valid.) - $(LI `ignore` Do not abort execution or print any cycles. Order of - static construction is implementation defined, and not guaranteed - to be valid.) + When cycles are present, order of static construction is + implementation defined, and not guaranteed to be valid.) + $(LI `ignore` Do not abort execution or print any cycles. When + cycles are present, order of static construction is implementation + defined, and not guaranteed to be valid.) ) $(H3 $(LNAME2 order_of_static_ctors, Order of Static Construction within a Module)) diff --git a/spec/version.dd b/spec/version.dd index 5964007d45..e5550068c2 100644 --- a/spec/version.dd +++ b/spec/version.dd @@ -279,6 +279,8 @@ $(H3 $(LEGACY_LNAME2 PredefinedVersions, predefined-versions, Predefined Version $(TROW $(ARGS $(D MIPS_HardFloat)) , $(ARGS The MIPS $(D hard-float) ABI)) $(TROW $(ARGS $(D NVPTX)) , $(ARGS The Nvidia Parallel Thread Execution (PTX) architecture, 32-bit)) $(TROW $(ARGS $(D NVPTX64)) , $(ARGS The Nvidia Parallel Thread Execution (PTX) architecture, 64-bit)) + $(TROW $(ARGS $(D RISCV32)) , $(ARGS The RISC-V architecture, 32-bit)) + $(TROW $(ARGS $(D RISCV64)) , $(ARGS The RISC-V architecture, 64-bit)) $(TROW $(ARGS $(D SPARC)) , $(ARGS The SPARC architecture, 32-bit)) $(TROW $(ARGS $(D SPARC_V8Plus)) , $(ARGS The SPARC v8+ ABI)) $(TROW $(ARGS $(D SPARC_SoftFloat)) , $(ARGS The SPARC soft float ABI)) diff --git a/std-ddox.ddoc b/std-ddox.ddoc index a2e3230bb1..5129a2961f 100644 --- a/std-ddox.ddoc +++ b/std-ddox.ddoc @@ -9,4 +9,5 @@ REF_ALTTEXT=$(DDOX_NAMED_REF $(REF_HELPER $+), $1) MREF_HELPER=$1$(DOT_PREFIXED $+) MREF=$(D $(MREF_HELPER $1, $+)) MREF_ALTTEXT=$(DDOX_NAMED_REF $(MREF_HELPER $+), $1) +DDOX_UNITTEST_HEADER= _= diff --git a/std_navbar-release.ddoc b/std_navbar-release.ddoc index 7899a22e2c..3712f926fe 100644 --- a/std_navbar-release.ddoc +++ b/std_navbar-release.ddoc @@ -13,3 +13,4 @@ $(SUBNAV_TEMPLATE $(UL $(MODULE_MENU)) ) _= +EXTRA_FOOTERS=$(SCRIPTLOAD $(STATIC js/run_examples.js)) |