Skip to content
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

Merged
merged 10 commits into from
Mar 19, 2024

Conversation

zygoloid
Copy link
Contributor

@zygoloid zygoloid commented Mar 19, 2024

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.

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 impls -- in particular, when we add support for AddWith, we'll need to update all the tests. The plan is to remove the script once things settle down.

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.
Copy link
Contributor

@jonmeow jonmeow left a 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?)

@@ -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)?$'
Copy link
Contributor

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?

Copy link
Contributor Author

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;
Copy link
Contributor

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"?

Copy link
Contributor Author

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>(
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 6 to 9
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
}
Copy link
Contributor

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.

Copy link
Contributor

@jonmeow jonmeow Mar 19, 2024

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]

Copy link
Contributor Author

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.

toolchain/check/operator.h Outdated Show resolved Hide resolved
toolchain/check/handle_operator.cpp Outdated Show resolved Hide resolved
toolchain/check/handle_operator.cpp Outdated Show resolved Hide resolved
return SemIR::NameScopeId::Invalid;
}

package_id = context.constant_values().Get(package_id).inst_id();
Copy link
Contributor

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Comment added.


namespace Carbon::Check {

// Returns the scope of the Carbon package, or Invalid if it's not found.
Copy link
Contributor

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)?

Copy link
Contributor Author

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();
Copy link
Contributor

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.

Copy link
Contributor Author

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>
toolchain/check/handle_operator.cpp Outdated Show resolved Hide resolved
toolchain/check/handle_operator.cpp Outdated Show resolved Hide resolved
toolchain/check/operator.h Outdated Show resolved Hide resolved
toolchain/check/operator.h Outdated Show resolved Hide resolved
Add TODO to remove script.

Minimize size of generated tests.
@zygoloid zygoloid enabled auto-merge March 19, 2024 18:49
@zygoloid zygoloid added this pull request to the merge queue Mar 19, 2024
Merged via the queue into carbon-language:trunk with commit cf361a8 Mar 19, 2024
7 checks passed
@zygoloid zygoloid deleted the toolchain-operators branch March 19, 2024 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants