Skip to content

Add STATIC_ASSERT_INCOMPLETE_TYPE to enforce include minimality. #107587

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Ivorforce
Copy link
Member

@Ivorforce Ivorforce commented Jun 16, 2025

Cross-includes are our main compile time problem right now. Not only does parsing take long, also every small change to a header file leads to hundreds of recompilations of compile units. It's time to do something.

STATIC_ASSERT_INCOMPLETE_TYPE is designed to protect ourselves against unnecessary includes. It lets us enforce certain couplings are not made, such as an include to dictionary.h from ustring.h. The assertion is made only for release build, to avoid interfering with the development cycle.
(This check was originally made for #107492, but I split it off to make it easier to discuss it separately).

I enforce 3 assumptions in this PR that already hold (no Dictionary include from String, and Dictionary and String from Array). There are many others we should be making. This is left to do as follow-up PRs (e.g. #106434).

@lawnjelly
Copy link
Member

Is a good idea as it really needs discussion of its own. It looks fine to me (barring the question about the define above).

@Repiteo
Copy link
Contributor

Repiteo commented Jun 17, 2025

We could expand this assert to headers by making the template reusable. The easiest way would probably be __COUNTER__; while technically non-standard, it's de facto standard like #pragma once and alloca(). Not sure if there's a use-case for that right away, but I can see it being handy as the includes become more fine-grained

@Ivorforce
Copy link
Member Author

Ivorforce commented Jun 17, 2025

We could expand this assert to headers by making the template reusable. The easiest way would probably be __COUNTER__; while technically non-standard, it's de facto standard like #pragma once and alloca(). Not sure if there's a use-case for that right away, but I can see it being handy as the includes become more fine-grained

I'm not sure I understand, could you expand on the idea?

Edit: Ohh, I think I get it. You mean probably something like this, right? (pseudocode)

// <my_header.h>
bool was_already_incomplete = is_incomplete_type_v<Type>();

#include "abc.h"
#include "def.h"
#include "gi.h"

if (!was_already_incomplete && is_incomplete_type_v<Type>()) {
    #error "Class was incomplete etc."
}

@lawnjelly
Copy link
Member

Edit: Ohh, I think I get it. You mean probably something like this, right (pseudocode)?

Does that actually achieve anything that can't be done by putting this in the cpp? Apart from making the builds more likely to fail due to change in inclusion order?

@Ivorforce
Copy link
Member Author

Ivorforce commented Jun 17, 2025

Edit: Ohh, I think I get it. You mean probably something like this, right (pseudocode)?

Does that actually achieve anything that can't be done by putting this in the cpp? Apart from making the builds more likely to fail due to change in inclusion order?

I don't think so. But it's clever! 😄

@Ivorforce Ivorforce force-pushed the static-assert-includes branch 2 times, most recently from bfce35b to 9bb4311 Compare June 17, 2025 12:10
Copy link
Member

@lawnjelly lawnjelly left a comment

Choose a reason for hiding this comment

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

Looks fine to me, but this will need a check by @akien-mga .

@Ivorforce Ivorforce requested a review from akien-mga June 17, 2025 16:12
Copy link
Contributor

@Repiteo Repiteo left a comment

Choose a reason for hiding this comment

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

When suggesting __COUNTER__ for assertion reuse, this is what I was referring to:

// typedefs.h

template <typename T, int Counter, typename = void>
struct is_fully_defined : std::false_type {};

template <typename T, int Counter>
struct is_fully_defined<T, Counter, std::void_t<decltype(sizeof(T))>> : std::true_type {};

template <typename T, int Counter>
constexpr bool is_fully_defined_v = is_fully_defined<T, Counter>::value;

#if !defined(SCU_BUILD_ENABLED) && !defined(DEV_ENABLED) && !defined(DEBUG_ENABLED)
/// Enforces the requirement that a class is not fully defined.
/// This can be used to reduce include coupling and keep compile times low.
/// The check must be made at the top of the corresponding .cpp file of a header.
#define STATIC_ASSERT_INCOMPLETE_CLASS(m_type) \
	class m_type;                              \
	static_assert(!is_fully_defined_v<m_type, __COUNTER__>, #m_type " was unexpectedly fully defined. Please check the include hierarchy of '" __FILE__ "' and remove includes that resolve the class.");
#else
#define STATIC_ASSERT_INCOMPLETE_CLASS(m_type)
#endif

...
// ustring.cpp

#include "ustring.h"

STATIC_ASSERT_INCOMPLETE_CLASS(Dictionary); // Expectation: pass.
// Old: !is_fully_defined_v<Dictionary>. Passes.
// New: !is_fully_defined_v<Dictionary, 0>. Passes.

#include "core/variant/dictionary.h"

STATIC_ASSERT_INCOMPLETE_CLASS(Dictionary); // Expectation: fail.
// Old: !is_fully_defined_v<Dictionary>. Reuses previous template. Passes.
// New: !is_fully_defined_v<Dictionary, 1>. Distinct template. Fails.

@Repiteo Repiteo modified the milestones: 4.x, 4.6 Jun 18, 2025
@Ivorforce
Copy link
Member Author

When suggesting __COUNTER__ for assertion reuse, this is what I was referring to

Oh, that makes a lot more sense than what I thought you meant.
I don't think we need it right now, but I agree — this is definitely worth keeping in mind for if this problem comes up.

Add enforcements against `Dictionary` for `ustring.h` and two for `Dictionary` and `String` from `array.h`.
@Ivorforce Ivorforce force-pushed the static-assert-includes branch from 9bb4311 to 12b7635 Compare June 20, 2025 09:09
@Ivorforce Ivorforce changed the title Add STATIC_ASSERT_INCOMPLETE_CLASS to enforce include minimality. Add STATIC_ASSERT_INCOMPLETE_TYPE to enforce include minimality. Jun 21, 2025
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