-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Overloaded operator support. #3796
Overloaded operator support. #3796
Conversation
Support is added for all overloaded operator interfaces in the current design apart from `Assign`, which is going to require some more work to properly handle, given that primitive assignment currently has a special implementation for quite a few builtin types. As we don't have support for generics yet -- in particular, generic interfaces -- there is no support for `*With` interfaces, but homogenous interfaces such as `Add` are supported instead. Factor out building of call expressions so that overloaded operators can generate calls. Switch a few places from using specific kinds of NodeId to a general NodeId. Because overloaded operators and other things like implicit conversions can result in member access and function calls, those operations can't require a specific kind of NodeId. Add import support for associated entities, and fix import support for interfaces and symbolic bindings. We now import interfaces in two steps, first importing a forward declaration then a definition, just like we do for classes. For symbolic bindings, we ensure that each BindSymbolicName is imported only once, because its ID is used as its symbolic identity. This is necessary because we (only) support operator interfaces that are defined in an imported Carbon package for now. The entire contents of `check/operator.cpp` should probably be rethought. In particular, doing a lot of name lookups on each operator is likely to be bad for performance. But this gets us to the point where overloaded operators are basically working, which seems like a good place to iterate from.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome!
Note regarding name lookup, I've had similar concerns -- I think we need some way of caching operator lookup results. But I'm not sure what the shape of that is. (do we store this on types?)
.pre-commit-config.yaml
Outdated
@@ -180,7 +180,7 @@ repos: | |||
Exceptions. See /LICENSE for license information. | |||
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |||
- --custom_format | |||
- '\.(carbon|proto|ypp)$' | |||
- '\.(carbon|proto|ypp)(\.in)?$' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's a ".in" file? Should this be covered by the PR description?
It looks like you're using it for a script, but it's still Carbon code. We use .def a bit for that in C++; would .def be appropriate here too? Maybe it should be .in.carbon instead of .carbon.in? Perhaps .template or .tmpl instead of .in, to be precise about the use-case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switched to .tmpl
. (.def
suggests C preprocessor input. .in.carbon
would get picked up by file_test
.)
|
||
// --- prelude.carbon | ||
|
||
package Carbon api; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why "Carbon"? If you're trying to create a package name conflict, should this be "Core"? If not, maybe something like "FakePrelude"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to Core
to match general switch from Carbon
to Core
.
@@ -273,8 +273,7 @@ struct BoolLiteral { | |||
// `self` parameter, such as `object.MethodName`. | |||
struct BoundMethod { | |||
static constexpr auto Kind = | |||
InstKind::BoundMethod.Define<Parse::AnyMemberAccessExprId>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you removing all uses of AnyMemberAccessExprId? Should the definition be deleted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
make_test() { | ||
HEADER="// This file was generated from $4. Run make_tests.sh to regenerate." | ||
sed "s,INTERFACE,$1,g; s,OP,$2,g; s,HEADER,$HEADER," $4 > $3.carbon | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have concerns about this script; putting a script in testdata feels like it hides it from developers (I would only expect data files here, not code). Additionally, it adds another manual execution step separate from autoupdate. Shell scripts are additionally a barrier if we try to support Windows -- I would suggest a preference for Python, as it has better cross-platform compatibility.
What alternatives had you considered? A few I might suggest if you haven't considered them already:
- Use a genrule to generate boilerplate files.
- file_test should be okay taking any BUILD output as input, so I think this would mostly work.
- You'd probably and to specify ARGS to exclude the IR (i.e., to avoid update issues, making this just a test that it compiles)
- genrules can still be written to be platform-specific, so please be careful about this -- I think we currently only have genrules in explorer code.
- Write a traditional unit test that generates content and runs the driver on it directly.
- This feels a little related to the genrule, although shifting away from file_test a little more. End results are probably the same.
- Add a utility in file_test for templating.
- A way to still have autoupdate support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to note, discussed this offline: agreed to a TODO to remove the script, with the expectation that the [actual] prelude would lead to these being pretty small and not worth autoupdate anymore.
[avoiding various template approaches to avoid abstraction of test files and golden output]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per discussion, added a TODO to say the script is short-term and should be removed once things stabilize here.
return SemIR::NameScopeId::Invalid; | ||
} | ||
|
||
package_id = context.constant_values().Get(package_id).inst_id(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this re-resolve through the constant? (a comment might help)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment added.
toolchain/check/operator.cpp
Outdated
|
||
namespace Carbon::Check { | ||
|
||
// Returns the scope of the Carbon package, or Invalid if it's not found. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why "Carbon" versus "Core"? Wasn't "Core" the outcome of #2113 (comment)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right. Switched to Core throughout.
We should have a proposal for that, and update the documentation to match :-)
return SemIR::InstId::Invalid; | ||
} | ||
|
||
op_id = context.constant_values().Get(op_id).inst_id(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to package_id comment, this feels odd.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment added.
Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Co-authored-by: Carbon Infra Bot <carbon-external-infra@google.com>
Add TODO to remove script. Minimize size of generated tests.
Support is added for all overloaded operator interfaces in the current design apart from
Assign
, which is going to require some more work to properly handle, given that primitive assignment currently has a special implementation for quite a few builtin types.As we don't have support for generics yet -- in particular, generic interfaces -- there is no support for
*With
interfaces, but homogenous interfaces such asAdd
are supported instead.Factor out building of call expressions so that overloaded operators can generate calls.
Switch a few places from using specific kinds of NodeId to a general NodeId. Because overloaded operators and other things like implicit conversions can result in member access and function calls, those operations can't require a specific kind of NodeId.
Add import support for associated entities, and fix import support for interfaces and symbolic bindings. We now import interfaces in two steps, first importing a forward declaration then a definition, just like we do for classes. For symbolic bindings, we ensure that each BindSymbolicName is imported only once, because its ID is used as its symbolic identity. This is necessary because we (only) support operator interfaces that are defined in an imported Carbon package for now.
The entire contents of
check/operator.cpp
should probably be rethought. In particular, doing a lot of name lookups on each operator is likely to be bad for performance. But this gets us to the point where overloaded operators are basically working, which seems like a good place to iterate from.For now, the tests that the individual operators map to the right interfaces are mostly generated by a script, but that's just because I'm expecting a fair bit of churn in how we define the prelude and the
impl
s -- in particular, when we add support forAddWith
, we'll need to update all the tests. The plan is to remove the script once things settle down.