Skip to content

Commit

Permalink
Starting over on function types - initial support for function types …
Browse files Browse the repository at this point in the history
…in <> lists (#1183)

* Starting over on function types - initial support for function types in <> lists

Should generally support std::function<> style uses

Aside: I'm happy to find that C++ allows parameter names in function types, thank you WG21! I hadn't seen those used in examples so I had been expecting that cppfront would have to suppress those, but std::function< int ( std::string& param_name ) > is legal code. Sweet.

This commit does not yet support pointer-to-function local variables...

* Add support for pointer-to-function variables

* Add docs examples for function typeids with std::function & *pfn

* Add support for pointer to function parameter types

* Support function type ids in type aliases
  • Loading branch information
hsutter authored Jul 27, 2024
1 parent 079b1b5 commit 212eb15
Show file tree
Hide file tree
Showing 19 changed files with 660 additions and 135 deletions.
2 changes: 1 addition & 1 deletion docs/cpp2/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Contract groups are useful to enable or disable or [set custom handlers](#violat
You can create new contract groups just by creating new objects that have a `.report_violation` function. The object's name is the contract group's name. The object can be at any scope: local, global, or heap.
For example, here are some ways to use contract groups of type [`cpp2::contract_group`](#violation_handlers), which is a convenient group type:
For example, here are some ways to use contract groups of type [`cpp2::contract_group`](#violation-handlers), which is a convenient group type:
``` cpp title="Using contract groups" hl_lines="1 4 6 10-12"
group_a: cpp2::contract_group = (); // a global group
Expand Down
34 changes: 28 additions & 6 deletions docs/cpp2/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ func: ( /* no parameters */ ) = { /* empty body */ }
```
## <a id="parameters"></a> Parameters
## <a id="function-signatures"></a> Function signatures: Parameters, returns, and using function types
### <a id="parameters"></a> Parameters
The parameter list is a [list](common.md#lists) enclosed by `(` `)` parentheses. Each parameter is declared using the [same unified syntax](declarations.md) as used for all declarations. For example:
Expand Down Expand Up @@ -72,7 +74,7 @@ wrap_f: (
```


## <a id="return-values"></a> Return values
### <a id="return-values"></a> Return values

A function can return either of the following. The default is `#!cpp -> void`.

Expand Down Expand Up @@ -152,7 +154,7 @@ main: () = {
```


### <a id="nodiscard-outputs"></a> Function outputs are not implicitly discardable
#### <a id="nodiscard-outputs"></a> Function outputs are not implicitly discardable

A function's outputs are its return values, and the "out" state of any `out` and `inout` parameters.

Expand Down Expand Up @@ -200,9 +202,29 @@ main: ()
> - A function call written in Cpp2 `x.f()` member call syntax always treats a non-`#!cpp void` return type as not discardable, even if the function was written in Cpp1 syntax that did not write `[[nodiscard]]`.

### <a id = "function-types"></a> Using function types

The same function parameter/return syntax can be used as a function type, for example to instantiate `std::function` or to declare a pointer to function variable. For example:

``` cpp title="Using function types with std::function and *pfunc" hl_lines="4 7"
decorate_int: (i: i32) -> std::string = "--> (i)$ <--";

main: () = {
pf1: std::function< (i: i32) -> std::string > = decorate_int&;
std::cout << "pf1(123) returned \"(pf1(123))$\"\n";

pf2: * (i: i32) -> std::string = decorate_int&;
std::cout << "pf2(456) returned \"(pf2(456))$\"\n";
}
// Prints:
// pf1 returned "--> 123 <--"
// pf2 returned "--> 456 <--"
```


## <a id="control flow"></a> Control flow

## <a id="branches"></a> `#!cpp if`, `#!cpp else` — Branches
### <a id="branches"></a> `#!cpp if`, `#!cpp else` — Branches

`if` and `else` are like always in C++, except that `(` `)` parentheses around the condition are not required. Instead, `{` `}` braces around a branch body *are* required. For example:

Expand All @@ -216,7 +238,7 @@ else {
```


## <a id="loops"></a> `#!cpp for`, `#!cpp while`, `#!cpp do` — Loops
### <a id="loops"></a> `#!cpp for`, `#!cpp while`, `#!cpp do` — Loops

**`#!cpp do`** and **`#!cpp while`** are like always in C++, except that `(` `)` parentheses around the condition are not required. Instead, `{` `}` braces around the loop body *are* required.

Expand Down Expand Up @@ -296,7 +318,7 @@ Line by line:
- `next i++`: The end-of-loop-iteration statement. Note `++` is always postfix in Cpp2.


### Loop names, `#!cpp break`, and `#!cpp continue`
#### Loop names, `#!cpp break`, and `#!cpp continue`

Loops can be named using the usual **name `:`** syntax that introduces all names, and `#!cpp break` and `#!cpp continue` can refer to those names. For example:

Expand Down
12 changes: 7 additions & 5 deletions include/cpp2util.h
Original file line number Diff line number Diff line change
Expand Up @@ -518,15 +518,17 @@ inline std::string join(List const& list) {
//
// Conveniences for expressing Cpp1 references (rarely useful)
//
// Note: Only needed in rare cases to take full control of matching a
// Cpp1 signature exactly. Most cases don't need this, for example
// a Cpp1 virtual function signature declaration like
// Note: Only needed in rare cases to take full control of matching an
// odd Cpp1 signature exactly. Most cases don't need this... for
// example, a Cpp1 virtual function signature declaration like
//
// virtual void f(int&) const
// virtual void myfunc(int& val) const
//
// can already be directly overriden by a Cpp2 declaration of
//
// f: (override this, inout val : int)
// myfunc: (override this, inout val: int)
// // identical to this in Cpp1 syntax:
// // void myfunc(int& val) const override
//
// without any need to say cpp1_ref on the int parameter.
//
Expand Down
102 changes: 102 additions & 0 deletions regression-tests/pure2-function-typeids.cpp2
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@

// --- Scaffolding

f: () = std::cout << "hello world!\n";

g_in : ( s: std::string) = std::cout << "Come in, (s)$\n";
g_inout: (inout s: std::string) = std::cout << "Come in awhile, but take some biscuits on your way out, (s)$!\n";
g_out : (out s: std::string) = s = "A Powerful Mage";
g_move : (move s: std::string) = std::cout << "I hear you've moving, (s)$?\n";

h_forward: (inout s: std::string) -> forward std::string = { std::cout << "Inout (s)$ ... "; return s; }
h_out : ( s: std::string) -> std::string = { std::cout << "In (s)$ ... "; return "yohoho"; }

f1: (a: std::function< (x:int) -> int >) -> int = a(1);
f2: (a: * (x:int) -> int ) -> int = a(2);
g : (x:int) -> int = x+42;


// --- Tests for type aliases

A_h_forward: type == (inout s: std::string) -> forward std::string;


main: () =
{
// --- Test basic/degenerate cases

// Test std::function< void() >
ff: std::function< () -> void > = f&;
ff();

// Ordinary pointer to function, deduced (always worked)
pf: * () -> void = f&;
pf();


// --- Tests for parameters
// Note: Not forward parameters which imply a template...
// function type-ids are for single function signatures

fg_in : std::function< ( s: std::string) -> void > = g_in&;
fg_inout: std::function< (inout s: std::string) -> void > = g_inout&;
fg_out : std::function< (out s: std::string) -> void > = g_out&;
fg_move : std::function< (move s: std::string) -> void > = g_move&;
pg_in : * ( s: std::string) -> void = g_in&;
pg_inout: * (inout s: std::string) -> void = g_inout&;
pg_out : * (out s: std::string) -> void = g_out&;
pg_move : * (move s: std::string) -> void = g_move&;

frodo: std::string = "Frodo";
sam : std::string = "Sam";

// Test in param
fg_in(frodo);
pg_in(sam);

// Test inout
fg_inout(frodo);
pg_inout(sam);

// Test out
gandalf : std::string;
galadriel: std::string;
fg_out(out gandalf);
std::cout << "fg_out initialized gandalf to: (gandalf)$\n";
pg_out(out galadriel);
std::cout << "pg_out initialized galadriel to: (galadriel)$\n";
gandalf = "Gandalf";
galadriel = "Galadriel";

// Test move
fg_move(frodo); // last use, so (move frodo) is not required
pg_move(sam); // last use, so (move sam) is not required


// --- Tests for single anonymous returns
// Note: Not multiple named return values... function-type-ids
// are for Cpp1-style (single anonymous, possibly void) returns

fh_forward: std::function< (inout s: std::string) -> forward std::string > = h_forward&;
fh_out : std::function< ( s: std::string) -> std::string > = h_out&;
ph_forward: * (inout s: std::string) -> forward std::string = h_forward&;
ph_out : * ( s: std::string) -> std::string = h_out&;

ph_forward2: * A_h_forward = h_forward&;

// Test forward return
std::cout << "fh_forward returned: (fh_forward(gandalf))$\n";
std::cout << "ph_forward returned: (ph_forward(galadriel))$\n";
std::cout << "ph_forward2 returned: (ph_forward2(galadriel))$\n";

// Test out return
std::cout << "fh_out returned: (fh_out(gandalf))$\n";
std::cout << "ph_out returned: (ph_out(galadriel))$\n";


// --- Tests for function parameters
std::cout << "(f1(g&))$\n";
std::cout << "(f2(g&))$\n";


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
hello world!
hello world!
Come in, Frodo
Come in, Sam
Come in awhile, but take some biscuits on your way out, Frodo!
Come in awhile, but take some biscuits on your way out, Sam!
fg_out initialized gandalf to: A Powerful Mage
pg_out initialized galadriel to: A Powerful Mage
I hear you've moving, Frodo?
I hear you've moving, Sam?
Inout Gandalf ... fh_forward returned: Gandalf
Inout Galadriel ... ph_forward returned: Galadriel
Inout Galadriel ... ph_forward2 returned: Galadriel
In Gandalf ... fh_out returned: yohoho
In Galadriel ... ph_out returned: yohoho
43
44
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ do
fi
done
rm -f *.obj *.exp *.lib
find . -type f -exec bash -c "[ ! -s \"{}\" ] && rm \"{}\"" \;
printf "\nDone: %s .cpp tests compiled\n" "$count"
printf "\n %s .cpp executables generated and run\n" "$exe_count"
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
hello world!
hello world!
Come in, Frodo
Come in, Sam
Come in awhile, but take some biscuits on your way out, Frodo!
Come in awhile, but take some biscuits on your way out, Sam!
fg_out initialized gandalf to: A Powerful Mage
pg_out initialized galadriel to: A Powerful Mage
I hear you've moving, Frodo?
I hear you've moving, Sam?
Inout Gandalf ... fh_forward returned: Gandalf
Inout Galadriel ... ph_forward returned: Galadriel
Inout Galadriel ... ph_forward2 returned: Galadriel
In Gandalf ... fh_out returned: yohoho
In Galadriel ... ph_out returned: yohoho
43
44
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ do
fi
done
rm -f *.obj *.exp *.lib
find . -type f -exec bash -c "[ ! -s \"{}\" ] && rm \"{}\"" \;
printf "\nDone: %s .cpp tests compiled\n" "$count"
printf "\n %s .cpp executables generated and run\n" "$exe_count"
Original file line number Diff line number Diff line change
@@ -1,41 +1,41 @@
In file included from mixed-bugfix-for-ufcs-non-local.cpp:6:
../../../include/cpp2util.h:2100:1: error: lambda-expression in template parameter type
2100 | class finally_success
2100 |
| ^
../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’
2137 | finally(finally&& that) noexcept
2137 | ~finally() noexcept { f(); }
| ^
mixed-bugfix-for-ufcs-non-local.cpp2:13:12: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’
mixed-bugfix-for-ufcs-non-local.cpp2:13:36: error: template argument 1 is invalid
../../../include/cpp2util.h:2100:1: error: lambda-expression in template parameter type
2100 | class finally_success
2100 |
| ^
../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’
2137 | finally(finally&& that) noexcept
2137 | ~finally() noexcept { f(); }
| ^
mixed-bugfix-for-ufcs-non-local.cpp2:21:12: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’
mixed-bugfix-for-ufcs-non-local.cpp2:21:36: error: template argument 1 is invalid
../../../include/cpp2util.h:2100:1: error: lambda-expression in template parameter type
2100 | class finally_success
2100 |
| ^
../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’
2137 | finally(finally&& that) noexcept
2137 | ~finally() noexcept { f(); }
| ^
mixed-bugfix-for-ufcs-non-local.cpp2:31:12: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’
mixed-bugfix-for-ufcs-non-local.cpp2:31:36: error: template argument 1 is invalid
../../../include/cpp2util.h:2100:1: error: lambda-expression in template parameter type
2100 | class finally_success
2100 |
| ^
../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’
2137 | finally(finally&& that) noexcept
2137 | ~finally() noexcept { f(); }
| ^
mixed-bugfix-for-ufcs-non-local.cpp2:33:12: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’
mixed-bugfix-for-ufcs-non-local.cpp2:33:36: error: template argument 1 is invalid
../../../include/cpp2util.h:2100:1: error: lambda-expression in template parameter type
2100 | class finally_success
2100 |
| ^
../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’
2137 | finally(finally&& that) noexcept
2137 | ~finally() noexcept { f(); }
| ^
mixed-bugfix-for-ufcs-non-local.cpp2:21:12: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’
mixed-bugfix-for-ufcs-non-local.cpp2:21:36: error: template argument 1 is invalid
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
hello world!
hello world!
Come in, Frodo
Come in, Sam
Come in awhile, but take some biscuits on your way out, Frodo!
Come in awhile, but take some biscuits on your way out, Sam!
fg_out initialized gandalf to: A Powerful Mage
pg_out initialized galadriel to: A Powerful Mage
I hear you've moving, Frodo?
I hear you've moving, Sam?
Inout Gandalf ... fh_forward returned: Gandalf
Inout Galadriel ... ph_forward returned: Galadriel
Inout Galadriel ... ph_forward2 returned: Galadriel
In Gandalf ... fh_out returned: yohoho
In Galadriel ... ph_out returned: yohoho
43
44
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ do
fi
done
rm -f *.obj *.exp *.lib
find . -type f -exec bash -c "[ ! -s \"{}\" ] && rm \"{}\"" \;
printf "\nDone: %s .cpp tests compiled\n" "$count"
printf "\n %s .cpp executables generated and run\n" "$exe_count"
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
hello world!
hello world!
Come in, Frodo
Come in, Sam
Come in awhile, but take some biscuits on your way out, Frodo!
Come in awhile, but take some biscuits on your way out, Sam!
fg_out initialized gandalf to: A Powerful Mage
pg_out initialized galadriel to: A Powerful Mage
I hear you've moving, Frodo?
I hear you've moving, Sam?
Inout Gandalf ... fh_forward returned: Gandalf
Inout Galadriel ... ph_forward returned: Galadriel
Inout Galadriel ... ph_forward2 returned: Galadriel
In Gandalf ... fh_out returned: yohoho
In Galadriel ... ph_out returned: yohoho
43
44
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pure2-function-typeids.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ for %%f in (*.cpp) do (
)
)
del pure2-*.obj mixed-*.obj *.exp *.lib
..\..\rm-empty-files.bat
echo.
echo Done: %count% .cpp tests compiled
echo.
Expand Down
Loading

8 comments on commit 212eb15

@JohelEGP
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's some lowering errors (https://cpp2.godbolt.org/z/b9T19oj17):

f: (copy _: i32) = { }

main: () = {
  // OK.
  x: * (copy x: i32) -> void = f;
  _ = x;

  // Error (when omitting `-> void`):
  // `-> void(*y)(cpp2::i32 x)  = f;`
  y: * (copy x: i32) = f;
  _ = y;

  // Error (when the object is anonymous):
  // `void(*_)(cpp2::i32 x) auto_1 = f;`
  //                        ^~~~~~
  _: * (copy x: i32) -> void = f;
}

@hsutter
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I'll take a look...

@JohelEGP
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Support is also missing for unnamed objects (https://cpp2.godbolt.org/z/94z75M196):

f: (copy _: i32) = { }
main: () = {
  _ = :* (copy x: i32) -> void = f;
}
auto f([[maybe_unused]] cpp2::i32 unnamed_param_1) -> void{}
auto main() -> int{
  static_cast<void>(void(cpp2::i32 x){f});
}
main.cpp2:3:36: error: expected '(' for function-style cast or type construction
    3 |   static_cast<void>(void(cpp2::i32 x){f});
      |                          ~~~~~~~~~ ^
main.cpp2:3:38: error: expected ')'
    3 |   static_cast<void>(void(cpp2::i32 x){f});
      |                                      ^
main.cpp2:3:20: note: to match this '('
    3 |   static_cast<void>(void(cpp2::i32 x){f});
      |                    ^
2 errors generated.

@JohelEGP
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about Cpp1 noexcept support?
There are API and performance use cases.
For example (https://cpp2.godbolt.org/z/G6ahnz5d9):

void f() noexcept { }
void g(void(*)()noexcept) { }
main: () = {
  x: * () -> void = f;
  g(x);
  _ = x;
}
main.cpp2:5:3: error: no matching function for call to 'g'
    5 |   g(x);
      |   ^
main.cpp2:2:6: note: candidate function not viable: no known conversion from 'void (*)()' to 'void (*)() noexcept' for 1st argument
    2 | void g(void(*)()noexcept) { }
      |      ^ ~~~~~~~~~~~~~~~~~
1 error generated.

For #526, I just allowed ! before the default throws specifier
(https://github.com/hsutter/cppfront/pull/526/files#diff-b5598a268a2d4647f67dac83fa5706e1326597671587bbc59115cf8a68f61c34R72-R73):

  // With `!throws`.
  _ = :* (copy _: std::string_view, copy _: CPP2_MESSAGE_PARAM) !throws = cpp2::report_and_terminate;

@JohelEGP
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Within a template argument list, a parameter can't be anonymous (https://cpp2.godbolt.org/z/41GP7ezzr):

f: (_: int) = { }
g: (_: int) -> std::function<(_: int) -> void> = f;
main: () = { }
auto f([[maybe_unused]] cpp2::impl::in<int> unnamed_param_1) -> void{}
[[nodiscard]] auto g([[maybe_unused]] cpp2::impl::in<int> unnamed_param_1) -> std::function<void([[maybe_unused]] cpp2::impl::in<int> unnamed_param_1)> { return f;  }
auto main() -> int{}
main.cpp2:2:99: error: expected variable name or 'this' in lambda capture list
    2 | [[nodiscard]] auto g([[maybe_unused]] cpp2::impl::in<int> unnamed_param_1) -> std::function<void([[maybe_unused]] cpp2::impl::in<int> unnamed_param_1)>;
      |                                                                                                   ^
main.cpp2:3:21: error: expected '>'
    3 | auto main() -> int{}
      |                     ^
main.cpp2:2:92: note: to match this '<'
    2 | [[nodiscard]] auto g([[maybe_unused]] cpp2::impl::in<int> unnamed_param_1) -> std::function<void([[maybe_unused]] cpp2::impl::in<int> unnamed_param_1)>;
      |                                                                                            ^
main.cpp2:4:1: error: expected function body after function declarator
    4 | 
      | ^
3 errors generated.

@JohelEGP
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also seems like you can't declare a function with a function type return type (https://cpp2.godbolt.org/z/hdfesPnbh):

f: () = { }
g: () -> * () -> void = f;
main: () = { }
main.cpp2(2,15): error: an explicit return value list cannot be empty (at '->')

https://cpp2.godbolt.org/z/5d5fefMEh:

f: (copy _: int) = { }
g: () -> * (copy _: int) -> void = f;
main: () = { }
main.cpp2(2,13): error: a return value cannot be 'copy' (at 'copy')
main.cpp2(2,13): error: missing function return after -> (at 'copy')

@JohelEGP
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to adapt https://github.com/hsutter/cppfront/pull/526/files#diff-b5598a268a2d4647f67dac83fa5706e1326597671587bbc59115cf8a68f61c34R100.
The pointer type is being lost (https://cpp2.godbolt.org/z/dK8TbddnW):

f:   (x: i32) -> std::type_identity_t<* (a: i32) -> std::string> = :(x: i32) -> std::string = "";
            [[nodiscard]] auto f(cpp2::impl::in<cpp2::i32> x) -> std::type_identity_t<std::string(cpp2::impl::in<cpp2::i32> a)>;
main.cpp2:2:27: error: function cannot return function type 'std::type_identity_t<std::string (cpp2::impl::in<cpp2::i32>)>' (aka 'std::string (int)')
    2 |             [[nodiscard]] auto f(cpp2::impl::in<cpp2::i32> x) -> std::type_identity_t<std::string(cpp2::impl::in<cpp2::i32> a)>;
      |                           ^

By the way, I think a function type should also be a type-id, so that this works:

  assert(f(42)     is     * (a: i32) -> std::string);
  assert(f(42)*     is      (a: i32) -> std::string);

@JohelEGP
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same as 212eb15#commitcomment-147043762 is happening in the main branch (as evidenced by the red CI):

diff --git a/regression-tests/test-results/clang-18-c++23-libcpp/pure2-last-use.cpp.output b/regression-tests/test-results/clang-18-c++23-libcpp/pure2-last-use.cpp.output
new file mode 100644
index 0000000..4162dcf
--- /dev/null
+++ b/regression-tests/test-results/clang-18-c++23-libcpp/pure2-last-use.cpp.output
@@ -0,0 +1,74 @@
+pure2-last-use.cpp2:273:36: error: expected variable name or 'this' in lambda capture list
+  273 |    public: std::add_pointer_t<int([[maybe_unused]] cpp2::impl::in<int> unnamed_param_1)> g; 
+      |                                    ^
+pure2-last-use.cpp2:329:2: error: expected '>'
+  329 | };
+      |  ^
+pure2-last-use.cpp2:273:30: note: to match this '<'
+  273 |    public: std::add_pointer_t<int([[maybe_unused]] cpp2::impl::in<int> unnamed_param_1)> g; 
+      |                              ^

Please sign in to comment.