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

Forward impl declarations of incomplete facet types #5219

Open
wants to merge 25 commits into
base: trunk
Choose a base branch
from

Conversation

josh11b
Copy link
Contributor

@josh11b josh11b commented Mar 29, 2025

Implements some of the changes from proposal #5168.

  • The data structure for complete facet types has been repurposed for identified facet types. Identified facet types are now a concept in the toolchain, but without named constraint support they are not substantially different from incomplete facet types.
  • Identified facet types keep the list of required specific interfaces in sorted order, for efficiency improvements in impl lookup. Found another way to identify the interface to impl (or number of impls if not 1).
  • Forward impl declarations of identified but incomplete facet types are allowed unless the facet type has rewrites. An incomplete facet type with rewrites is already either an error or has more than one interface and so can't be implemented, so this case can't be exercised very well yet.
  • Forward impl declarations of interface without rewrites use a placeholder inst block for the witness.
  • Changed some machinery to use RequireIdentifiedFacetType to access the interfaces of the facet type so we only need to add support for expanding named constraints into interfaces in one place.

@github-actions github-actions bot requested a review from danakj March 29, 2025 03:18
// this.
CARBON_CHECK(is_definition);
CARBON_DIAGNOSTIC(ImplAsIncompleteFacetTypeDefinition, Error,
"impl as incomplete facet type {0} definition",
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 trouble reading this grammatically, so suggesting something else:

Suggested change
"impl as incomplete facet type {0} definition",
"`impl as` target has facet type {0} that is not yet defined",

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was trying to convey that impl definitions specifically require the facet type to be complete (now unlike impl declarations).

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe something like definition of impl as incomplete facet type {0}?

// TODO: Finish facet type resolution. This code currently only handles
// rewrite constraints that set associated constants to a concrete value.
// Need logic to topologically sort rewrites to respect dependencies, and
// afterwards reject duplicates that are not identical.

auto facet_type_id =
context.types().GetTypeIdForTypeInstId(facet_type_inst_id);
CARBON_CHECK(facet_type_id != SemIR::ErrorInst::SingletonTypeId);
Copy link
Contributor

Choose a reason for hiding this comment

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

Generally we want to pass errors along through the system, how come we want a CHECK instead of a return here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the case should be unreachable here.

context, witness_loc_id,
{.type_id =
GetSingletonType(context, SemIR::WitnessType::SingletonInstId),
.elements_id = context.inst_blocks().AddPlaceholder(),
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we should have a special InstBlockId for this to differentiate from an empty block?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These ids should not be distinguished. This is a block that will be filled in later without changing its id. I've added a comment.

// require Self impls Y;
}

// Classes must be defined before being used in a function definition.
Copy link
Contributor

Choose a reason for hiding this comment

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

There's a test for this somewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure, but it is not the topic of this PR. This comment I believe is copied from the proposal.

// set of interfaces the facet type requires is known. Since named constraints
// are not yet supported, this currently never fails. Eventually this function
// will be passed a diagnoser for facet types that use some incomplete named
// constraint, and return `None` in that case.
Copy link
Contributor

Choose a reason for hiding this comment

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

Mention that this ensures the FacetType is present in identified_facet_types() when it returns non-None?

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.

// the facet type to the right of an `impl`...`as`, or `None` if no such
// single interface.
InterfaceId interface_id;
union {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use a std::variant so that we get checks that verify we don't access the wrong one and make UB?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that would mean extra storage for the discriminator.

// The single interface from `required_interfaces` to implement if this is
// the facet type to the right of an `impl`...`as`, or `None` if no such
// single interface.
InterfaceId interface_id;
Copy link
Contributor

Choose a reason for hiding this comment

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

These fields are duplicating the first element of required_interfaces, but adding a bunch of complexity for using the IntentifiedFacetType.

I was wondering if a method would suit our purposes just as well, without causing us to use variants/unions and so on?

Something like this?

auto is_impl_as_target() -> bool {
  return required_interfaces.size() == 1;
}
auto impl_as_target_interface( ) -> SpecificInterface {
  if (is_impl_as_target())  { return required_interfaces.front(); }
  else { return None; }
}

Or do you want to use these to hide the num_to_impl specifically when it's not used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One of my goals for this PR is to have the identified interfaces be a sorted list of all the required interfaces, mixing together the ones to implement. I'm hoping to follow this up with a PR to support where .Self impls J, at which point the one to implement won't necessarily be first. Adding some accessors to hide this complexity and maintain the invariants is a good idea though.

@github-advanced-security

This comment was marked as off-topic.

// this.
CARBON_CHECK(is_definition);
CARBON_DIAGNOSTIC(ImplAsIncompleteFacetTypeDefinition, Error,
"impl as incomplete facet type {0} definition",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was trying to convey that impl definitions specifically require the facet type to be complete (now unlike impl declarations).

// TODO: Finish facet type resolution. This code currently only handles
// rewrite constraints that set associated constants to a concrete value.
// Need logic to topologically sort rewrites to respect dependencies, and
// afterwards reject duplicates that are not identical.

auto facet_type_id =
context.types().GetTypeIdForTypeInstId(facet_type_inst_id);
CARBON_CHECK(facet_type_id != SemIR::ErrorInst::SingletonTypeId);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the case should be unreachable here.

context, witness_loc_id,
{.type_id =
GetSingletonType(context, SemIR::WitnessType::SingletonInstId),
.elements_id = context.inst_blocks().AddPlaceholder(),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These ids should not be distinguished. This is a block that will be filled in later without changing its id. I've added a comment.

}
const auto& complete = context.complete_facet_types().Get(complete_id);
if (complete.num_to_impl != 1) {
auto identified_id = RequireIdentifiedFacetType(context, *facet_type);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm happy to change the terminology, but that is really a topic for the proposal. I just haven't heard a clearer term. I feel like "identified" is less misleading than "fully declared" and I think users will have fewer preconceived ideas what "identified" means so it is more open for us to give it our own meaning.

// required to be complete.
if (witness.elements_id != SemIR::InstBlockId::Empty &&
witness_block.empty()) {
if (!RequireCompleteFacetTypeForImplDefinition(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the other case, it was already checked in InitialFacetTypeImplWitness.

impl_info.interface = *required_interface;
}

impl_info.interface = CheckConstraintIsInterface(context, impl_info);
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. It required changing the signature of CheckConstraintIsInterface, but that wasn't a problem.

// CHECK:STDERR: fail_extend_partially_defined_interface.carbon:[[@LINE-5]]:1: note: interface is currently being defined [InterfaceIncompleteWithinDefinition]
// CHECK:STDERR: interface I {
// CHECK:STDERR: ^~~~~~~~~~~~~
// CHECK:STDERR:
// CHECK:STDERR: fail_extend_partially_defined_interface.carbon:[[@LINE+4]]:5: error: impl declared but not defined [ImplMissingDefinition]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, I'm definitely missing code to enforce I is complete in this case. So that is now added. I don't know about your other question.

@@ -0,0 +1,2867 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just realized we abbreviate "declarations", so I'll rename this file before merging.

interface X;

// Allowed to use incomplete interfaces in function declarations.
fn F(U:! X);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added.

// require Self impls Y;
}

// Classes must be defined before being used in a function definition.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure, but it is not the topic of this PR. This comment I believe is copied from the proposal.

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.

4 participants