Releases: hsutter/cppfront
v0.8.0
Highlights
Changed license to Apache 2.0 with LLVM Exception
With the changes below, notably to make the remaining tweaks for function syntax, I think version 0.8 is now stable enough to be ready for a license that allows commercial use. Thank you again to everyone who provided feedback and PRs so far!
Note: This project is still 'without warranty / use at your own risk' and clearly says pre-1.0 right on the tin. You Have Been Reminded!
Regularized function syntax: Require =
for all functions, and defaulted single-expression bodies to -> forward _
Requiring =
even for the super-terse notation has been a widely-requested change, and I think it does make the code clearer to read.
For example, f: () expr;
must now be written as f: () = expr;
For a function whose body is a single expression, the default return type (i.e., if not specified) is now -> forward _
, which is Cpp1 -> decltype(auto)
(which, importantly, can deduce a value).
With this change, single-expression function bodies without { }
are still legal for any function, but as of this commit we have a clearer distinction in their use (which is reflected in the updates to the regression tests and other Cpp2 code in this commit):
-
It further encourages single-expression function bodies without
{ }
for unnamed function expressions (lambdas), by making more of those cases Do the Right Thing that the programmer intended. -
It naturally discourages their overuse for named functions, because it will more often cause a compiler warning or error:
-
a warning that callers are not using a returned results, when the function is called without using its value
-
an error that a deduced return type makes the function order-dependent, when the function is called from earlier in the source file... this is because a deduced return types creates a dependency on the body, and it is inherent (not an artifact of either Cpp1 or Cpp2)
But for those who liked that style, fear not, usually the answer is to just put the two characters
{ }
around the body... see examples in this commit, which I have to admit most are probably more readable even though I'm one of the ones who liked omitting the braces. -
Note: I realize that further evolution may not allow the
{ }
-free style for named functions. That may well be a reasonable further outcome.
I think both of these are positive improvements.
Added regular _ref
option to in
param and forward _
return; remove oddity inout: const
This keeps things nicely orthogonal addresses the two open issues with parameter passing:
- the now-removed oddity of
inout : const
, which becomes justin_ref
- the now-addressed need to directly express both Cpp1
-> decltype(auto)
and-> auto&&
, which now are-> forward _
and-> forward_ref _
Style | Parameter | Return |
---|---|---|
in |
⭐ | |
inout |
✅ | |
out |
✅ | |
copy |
✅ | |
move |
✅ | ✅ |
forward |
✅ | ⭐ |
The two ⭐'s are the cases that automatically pass/return by value or reference:
- all uses of
in
- deduced uses of
-> forward _
(ordinary-> forward specific_type
always returns by reference, adding constness if there's anin this
parameter)
Now both ⭐ cases also provide a _ref
option to not choose by-value.
An example that illustrates both is std::min
:
min: (in_ref a, in_ref b) -> forward_ref _
= { if b < a { return b; } else { return a; } }
main: (args) = {
x := 456;
y := 123;
std::cout << min(x, y) << '\n';
}
The container<T>::operator[]
case could already be written like this, where the first return lowers to Cpp1 T const&
and the second to T&
:
container: <T> type = {
buf: std::array<T, 10> = ();
operator[]: ( this, idx: i32) -> forward T = buf[idx];
operator[]: (inout this, idx: i32) -> forward T = buf[idx];
}
main: (args) = {
v: container<int> = ();
std::cout << v[0] << '\n';
}
Added support for decltype()
and type_of()
as a type-id
Thanks to @JohelEGP for this PR!
Support heterogeneous ..<
and ..=
ranges when there's a common type
Thanks @bluetarpmedia for this PR!
Renamed unsafe_*
to unchecked_*
, and recommend discard instead of as void
For renaming unsafe_*
" Thanks to Redditor @tialaramex for their suggestion, persuasively citing Rust's naming style (and thanks to the Rust designers too for the inspiration).
For replacing as void
: Thanks to @Velocirobtor and @JohelEGP for their suggestions on improving this diagnostic in the #1279 comment thread.
Added @hashable
metafunction
Super basic, adds a hash() -> size_t
function that has memberwise semantics, and uses std::hash
for data members and base::hash
for base classes.
Made forward
parameters of concrete type accept convertible/derived arguments
Made @struct
constructors use forward
parameters
A forward
parameter with a concrete type used to require an exact match, but can now accept arguments of convertible/derived types.
Code like this continues to work:
data: @struct type = { name: std::string; city: std::string; }
x: data = ( "Henry", "Memphis" ); // ok, uses conversions
I also added a @struct<noforward>
option to allow using @struct
with GCC 10, which doesn't support forward
parameters. And I use that in cppfront's own sources, just so cppfront itself continues building under GCC 10. (Someday we'll drop GCC 10 as a supported compiler, but for now it's still doing pretty well except for the won't-backport requires
-clause bug...)
Note that allowing the generated forwarding reference to bind to arguments of derived types was also suggested by @brevzin of @jumptrading in P2481 "Forwarding reference to specific type/template" -- thanks Barry, for the Cpp2 suggestion and for the ISO C++ forward
parameter proposal paper!
More examples:
Base: @polymorphic_base type = { } // a Base type
Derived: type = { this: Base; } // a Base-derived type
b: Base = ();
d: Derived = ();
s: std::string = ();
f:(forward _: Base ) = { } // forward a Base object
g:(forward _: std::string) = { } // forward a std::string object
main: () = {
f( b ); // ok, b is-a Base
f( d ); // ok, d is-a Base
f( s ); // ERROR, s is-not-a Base
g( s ); // ok, s is-a std::string
g( "xyzzy" ); // ok, "xyzzy" is-convertible-to std::string
g( b ); // ERROR, b is-not-a std::string
}
Cleaned up is
/as
implementation for std::variant
, std::optional
, std::any
Thanks to @filipsajdak for these PRs!
Added initial clang-tidy configuration
Thanks for @farmerpiki for this PR!
Additional PRs (thanks!)
- Don't build and run tests for docs-only changes. by @gregmarr in #1275
- Add passing styles to single type return by @gregmarr in #1274
- CI Update default arg tests by @jarzec in #1263
- Fix doc typo: Updated
..<
instead of...
for range operations in doc common.md by @tsoj in #1254 - CI Update the GitHub checkout action by @jarzec in #1299
- Exclude MSVC workaround on clang and gcc by @gregmarr in #1306
- CI Update tests - make all tests pass by @jarzec in #1324
New Contributors
- @farmerpiki made their first contribution in #1253
Full Changelog: v0.7.4...v0.8.0
V0.7.4
Highlights
Expanded compile-time support for is
Thanks to @filipsajdak for this PR! The built-in is
implementation is now constexpr
, and cases that are known statically now produce compile-time values.
Unified using
syntax
I had been using the Cpp1 syntax since I had nothing substantially better. But that did leave the grammar divergence between the two forms:
// Cpp1 syntax, and previous Cpp2: Two different syntaxes/grammars
using std::vector; // use one thing from std
using namespace std; // use everything in std
The historical reason for the two syntaxes is that the syntax using possibly::qualified::identifier;
hid that identifier
could be a function/type name or a namespace name, and it's good to have some visual distinction that we're pulling in all the declarations in a namespace.
But then I realized that this was another place we could apply the general Cpp2 _
"don't-care" wildcard, so that instead of using namespace NNN ;
we now write using NNN::_ ;
which is still visually explicit that we're pulling in all the declarations in a namespace but doesn't require a second special one-off syntax/grammar. Now things are more consistent:
// Now in Cpp2: One unified syntax using '_' wildcard
using std::vector; // use one thing from std
using std::_ ; // use everything in std
Of course the latter still lowers to Cpp1 using namespace std;
.
Style note: I find myself putting a space between _ ;
to make it more visually pleasing, so it's possible we may discover that the syntax is too subtle if people frequently write it without the space and then miss the _
. But it seems to be a nice unification to remove yet another grammar special case and still clearly express what's wanted using a generalization we already have in the _
wildcard... as Bjarne recommends, 'simplification through generalization.'
Support .sum
and .contains
for ..=
and ..<
ranges
Thanks to @brevzin for the suggestion! Examples:
std::cout << " ((1 ..= 20).sum())$ \n" // prints 210
" ((1 ..< 20).sum())$ \n" // prints 190
" ((1 ..= 20).contains(0))$ \n" // prints false
" ((1 ..= 20).contains(1))$ \n"; // prints true
Implementation detail: These were added as nonmembers, which is fine because UFCS finds them.
Enabled local function default arguments and cpp2::gcc_clang_msvc_min_versions()
This can be useful with 'if constexpr' to disable code known not to work on some otherwise-supported compilers (without macros). For example:
// Disable tests on lower-level compilers that have blocking bugs:
// GCC 14.00 or higher, Clang 16.00 or higher, MSVC 19.20 or higher
[]<auto V = gcc_clang_msvc_min_versions(1400, 1600, 1920)> () { if constexpr (V) {
// ... tests that would fail due to older compilers' bugs ...
}}();
Enabled metafunctions to add runtime support headers
Thanks to @bluetarpmedia (Neil Henderson) and @MaxSagebaum for requesting this and providing an initial implementation to build on, respectively.
This was motivated by the @regex
metafunction, which adds run-time support for actually executing the generated regex. It's provided via shared (header) code rather than by injecting a copy of the run-time support code into every translation unit that uses it, which makes sense because it's about 170K of code!
Previously, cpp2util.h
(the run-time support library) included everything for all metafunctions, including for @regex
. This had two problems: (1) Users paid for including/parsing it even if they never used that metafunction, which violates the "don't pay for it if you don't use it" zero-overhead principle. In this case, it roughly doubled the cost of compiling cpp2util.h
for all cppfront users even if they didn't use @regex
. (2) We eventually want to support users writing their own metafunctions, and this solution was hardwired into cppfront in-the-box metafunctions only.
With this change, a metafunction can say "ah, you ran me and so you're going to need the lowered Cpp1 file to include my run-time support code." For @regex
that is now spelled:
t.add_runtime_support_include( "cpp2regex.h" );
Although as of yet metafunctions must be linked into cppfront, this approach isn't specific to cppfront and will extend better when we do support metafunctions that are written in user code.
Made the docs home page more discoverable
The home page used to go to the first page of the "Welcome" section. That was fine on desktop browsers because the left-hand navigation still showed the whole doc structure. However, on phones and tablets, users reported that this made the home page look like there was only a little "Welcome" information because the rest was hidden behind first having to know to click on the hamburger menu and then second additionally seeing the left-arrow and clicking on that to reveal more of the documentation.
This change makes the home page show the entire documentation so it's easily visible on all devices.
Special thanks to PRs from...
- New contributor @jamadagni for docs typo fixes and improvements
- @filipsajdak for further improving
is
andas
, and fixing generation of is-constraints on wildcarded (generic) parameters - @MaxSagebaum for separating
@regex
metafunction and runtime code - @jarzec for ongoing CI improvements and updates
Full Changelog: v0.7.3...v0.7.4
v0.7.3
What's Changed: Highlights
Note: The first two were actually in 0.7.2 but I forgot to mention them in the 0.7.2 release notes.
Allow is
constraints on wildcarded parameters and locals.
The following now works (and the last line would be an error if not commented out):
print: (r: _ is std::regular) = {
std::cout << "satisfies std::regular\n";
}
print: (_) = {
std::cout << "fallback\n";
}
irregular: type = {}
main: () = {
print(42); // prints: satisfies std::regular
print(irregular()); // prints: fallback
ok : _ is std::regular = 42;
//err: _ is std::regular = irregular();
}
Always report file/line/function for bounds/null violations when <source_location>
is available
Bounds/null violations and other contract check failures now always report the violating file and line number and function name, if the Cpp1 compiler supports header <source_location>.
This means that on most Cpp1 compilers this code now gives the specific-line-attribution error by default:
main: () = {
vec: std::vector = (1, 3, 5, 2, 4, 6);
for 1 ..= vec.ssize() do (i) { // oops, off-by-one error
std::cout << vec[i]; // bounds violation caught
}
}
// On MSVC 2022, prints:
// 35246demo.cpp2(4) int __cdecl main(void): Bounds safety
// violation: out of bounds access attempt detected -
// attempted access at index 6, [min,max] range is [0,5]
// On GCC 14, prints:
// 35246demo.cpp2(4) int main(): Bounds safety violation:
// out of bounds access attempt detected - attempted access
// at index 6, [min,max] range is [0,5]
Support latest C++26 headers: <inplace_vector>
Cppfront's -import-std
and -include-std
options to import/include the entire C++ standard library tracks the latest headers added to ISO C++, and <inplace_vector>
was adopted at the June 2024 meeting..
Added integer division-by-zero checks
These work similarly to the null-dereference and bounds checks: They're on by default but you can turn them off with a -no
switch. Note the check happens only if the numerator and denominator are both integers. For example:
divide_42_by: (i) 42/i;
main: () = {
std::cout << divide_42_by(2); // ok
std::cout << divide_42_by(0); // causes div-by-0 in callee (line 1)
}
// On MSVC 2022, prints:
// 21demo.cpp2(1) auto __cdecl divide_42_by<int>(const int &):
// Type safety violation: integer division by zero attempt detected
// On GCC 14, prints:
// 21demo.cpp2(1) auto divide_42_by(const auto:256&) [with
// auto:256 = int]: Type safety violation: integer division by
// zero attempt detected
As with the null-deref and bounds checks, these are integrated with Cpp2 contracts. For this case, a violation is treated as a cpp2::type_safety
violation. The default semantics for cpp2::type_safety
are to terminate, but you can install a violation handler using .set_handler()
to make it do whatever you need (including to integrate into an existing logging/reporting framework you might have).
Made range operators explicit about whether the last value is included
The original design was to support ...
(implicitly half-open, last value is excluded) and ..=
(explicitly closed, last value is included). The former syntax has been changed to ..<
(explicitly half-open, last value is excluded) and the latter remains ..=
(explicitly closed, last value is included). So now both are visually explicit.
Rationale: Originally I made ...
the default because it's the safe default, for common cases like iterator ranges where including the last element is a bounds violation error. It would be creating a terrible pitfall to make the "default" syntax be an out-of-bounds error on every use with iterators.
However, feedback on Reddit and elsewhere quickly pointed out that for numeric ranges ...
not including the last value is also surprising. What to do?
One way out is to not support iterators. However, that would be a needless loss of functionality if there was a better answer.
And there is: A better way out is to simply embrace that there should not be a default... just make it convenient for programmers to be explicit every time about whether the last element is included or not. Supporting ..<
and ..=
as the range operators achieves that goal, I think. This change avoids all user surprise (WYSIWYG) and it avoids overloading the meaning of ...
which is also used for variable-length argument lists / fold-expressions / pack expansions.
I appreciate the feedback, and I think it led to a superior design for a C++-compatible ecosystem that heavily uses iterators today. Thanks to everyone who gave feedback!
Restrict unsafe_narrow
, add unsafe_cast
Restrict unsafe_narrow
to narrowing cases and arithmetic types.
Example:
f: (i: i32, inout s: std::string) = {
// j := i as i16; // error, maybe-lossy narrowing
j := unsafe_narrow<i16>(i); // ok, 'unsafe' is explicit
pv: *void = s&;
// pi := pv as *std::string; // error, unsafe cast
ps := unsafe_cast<*std::string>(pv); // ok, 'unsafe' is explicit
ps* = "plugh";
}
main: () = {
str: std::string = "xyzzy";
f( 42, str );
std::cout << str; // prints: plush
}
For operator++
/operator--
, generate return-old-value overload only if the type is copyable
Added to_string
/from_string
and to_code
/from_code
for @enum
and @flag_enum
types
The first is for humans, and uses unqualified names and ( flag1, flag2 )
lists for flag enums.
The second (by request) is to facilitate reflection + code generation in metafunctions, and uses qualified names and ( my_type::flag1 | my_type::flag2 )
lists for flag enums.
Added type_of(expr)
as a convenience synonym for std::remove_cvref_t<decltype(x)>
There was already a CPP2_TYPEOF
macro for that which is heavily used, and this feature lowers to that macro.
Allow function parameter default arguments
I had been experimenting with not allowing default arguments for function parameters, in part because of the potential for creating order-dependent code; the way to find out whether they're necessary is to not support them and see if that leaves a usability hole.
The result of the experiment is: We want it. There's been persistent feedback that default arguments are often useful, and are actually necessary for a few cases including particularly std::source_location
parameters. As for order independence, there are already ways to opt into creating potentially order-dependent code (such as by deduced return types which depend on function bodies). So I think it's time to enable default arguments, and Cpp2 is still order-independent by default.
This example now works (on Cpp1 compilers that also support <source_location>
:
my_function_name: (
fn: *const char = std::source_location::current().function_name()
)
= {
std::cout << "calling: (fn)$\n";
}
main: (args) = {
my_function_name();
}
// On MSVC 2022, prints:
// calling: int __cdecl main(const int,char **)
// On GCC 14, prints:
// calling: int main(int, char**)
Special thanks to PRs from:
- New contributor @tsoj for the enumeration
from_string
PR!
Full Changelog: v0.7.2...v0.7.3
v0.7.2
What's Changed: Highlights
- Add range operators, e.g.,
iter1 ... iter2
or1 ..= 100
(see docs:...
and..=
range operators) - Add
@regex
metafunction by @MaxSagebaum (docs in progress) - Add support for function types, e.g.,
std::function< (i: inout int) -> forward std::string >
and pointers to functions likepfn: *(i: inout int) -> forward std::string
(see docs: Using function types) - Add support for C++23 (P2290) delimited hexadecimal escapes of the from
\x{62}
, you can use them in Cpp2 code and they'll work if your Cpp1 compiler understands them
Special thanks to PRs from:
- @MaxSagebaum for
@regex
and\x{62}
- @JohelEGP for all his contributions toward function types, and many many more contributions over the past year (42 already-merged PRs)
- @jarzec for improving and maintaining CI so beautifully
- @sookach for improvements to command-line handling and the new range operators
- and to many more for all their regular helpful issues and review comments!
Full Changelog: v0.7.1...v0.7.2
v0.7.1
What's Changed: Highlights
- Add
..
syntax to invoke only a member function (no UFCS), e.g.,mystring..append("foo")
- Allow
x: const = init;
andx: * = init;
without_
type wildcard - Allow concatenated string literals, e.g.,
"Hello " "(name$)\n"
- Faster compile time when doing heavy reflection and code generation
- Ensure definite first use initialization cannot happen inside loops
- Better diagnostics for illegal UFCS calls
- Move-from-last-use only for moveable types
- Remove whitespace sensitivity for
*
and&
- Allow extra trailing commas in more places (
assert
) - Support latest GCC and Clang
- Added
-quiet
- Added
-cwd
New Contributors: Thanks again, and welcome!
- @dutkalex made their first contribution in #1044
- @matgla made their first contribution in #1045
- @alvarogalloc made their first contribution in #1081
- @sookach made their first contribution in #1100
- @helmesjo made their first contribution in #1024
Full Changelog: v0.7.0...v0.7.1