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

dyno: Make some improvements to const checking for associated actions #24734

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

dlongnecke-cray
Copy link
Contributor

@dlongnecke-cray dlongnecke-cray commented Mar 30, 2024

This PR introduces some const checking for associated actions such as assignment or init copy.

To help facilitate the changes, it adjusts the Access type in maybe-const.cpp so that it is now a wrapper around a QualifiedType. The enum values have been changed into methods. The Access type now provides an abstracted view where isRef does not necessarily imply isConstRef, and isConst applies to values as well (previously, the code only cared about constness for "ref" entities, and values were entirely ignored).

Rename isTypeDefaultInitializable to isTypeWithDefaultValue, as this is more abstract, and not all types are necessarily types with a "default initializer".

Add Type::isMutatedOnCopy and Type::isMutatedOnAssignment. These determine whether or not default-generated init= and = need to take the RHS by ref intent.

Add a new test file testTypeProperties.cpp which can be used to test things like isTypeWithDefaultValue.

FUTURE WORK

  • Introduce an error class for const checking.
  • Combine the logic used to break down associated actions and the code used to check call actuals, as they are somewhat similar.
  • Handle recursive classes, compilerError'd initializers, and WHERE_TBD in isTypeWithDefaultValue.
  • Introduce isDefaultInitializableComposite as a separate user-facing query, as we may need to determine if a class type can be default initialized when evaluating a new expression.
  • Figure out how to determine if a type has a default initializer without entering a big cycle during call resolution: when resolving an initializer call, we automatically inject compiler-generated candidates if needed. The candidates are only injected if they are needed, and that determination itself needs to rely on isTypeWithDefaultValue...... One option here could be to have canDefaultInitCompositeQuery not rely on regular call resolution when searching for candidates.

Signed-off-by: David Longnecker <dlongnecke-cray@users.noreply.github.com>
Signed-off-by: David Longnecker <dlongnecke-cray@users.noreply.github.com>
Signed-off-by: David Longnecker <dlongnecke-cray@users.noreply.github.com>
@mppf
Copy link
Member

mppf commented Apr 4, 2024

Figure out how to determine if a type has a default initializer without entering a big cycle during call resolution: when resolving an initializer call, we automatically inject compiler-generated candidates if needed. The candidates are only injected if they are needed, and that determination itself needs to rely on isTypeWithDefaultValue...... One option here could be to have canDefaultInitCompositeQuery not rely on regular call resolution when searching for candidates.

It's very important that such determinations about whether or not to add a default function be made with only information available in the module containing the type. The production compiler does it with some hacky ways to check for methods with certain names. Arguably, doing a regular resolution check is an improvement upon that. But either way, we can't have it do something like depend on point-of-instantiation.

Anyway, another option here to avoid the cycle you describe is to do the call resolution for a test call but ignore compiler-generated candidates when doing so. That doesn't seem too far afield to me because we'll also want to ignore POI while doing this check.

Determine if a type is mutated when it is copied. For example, any record
containing an 'owned' class field is mutated when copied using a
default copy-initializer. Any type attributed with 'PRAGMA_COPY_MUTATES'
is considered to be mutated on copy.
Copy link
Member

Choose a reason for hiding this comment

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

IMO it would be nice for this comment to mention init= somewhere

const uast::Decl* formalAst = nullptr;
bool typeHasDefault = isTypeWithDefaultValue(context, fieldQt.type());
bool formalHasDefault = rf.fieldHasDefaultValue(i) || typeHasDefault;
Copy link
Member

Choose a reason for hiding this comment

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

put typeHasDefault first in the || since we just computed it, so perhaps we don't have to use fieldHasDefaultvalue ?

const PoiScope* poiScope) {
static const TypedFnSignature*
tryResolveZeroArgMethod(Context* context, UniqueString name,
const AstNode* astForScopeOrErr,
Copy link
Member

Choose a reason for hiding this comment

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

To make the query using this more reusable, let's change astForScopeOrErr into an ID since that is all that is needed within

// TODO: Skipping avoids a recursive query but doesn't handle
// mutually recursive classes.
if (ft == t) continue;
const uast::AstNode* ast = nullptr;
Copy link
Member

Choose a reason for hiding this comment

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

see my above comment -- I'd like us to remove this variable / the use of idToAst.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should I plumb this id only all the way down into resolveGeneratedCall or just stop in the helpers?

Copy link
Member

Choose a reason for hiding this comment

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

resolveGeneratedCall only uses it for resolveFnCallSpecial, and there I think the only queries using it as an argument are ones that only run when we encounter an error. As a result, I don't think it's important to change resolveGeneratedCall in this way right now (but I would want this change if one day it became a query).

if (t == CompositeType::getRangeType(context)) return true;
if (t == CompositeType::getLocaleType(context)) return true;
if (t->isPrimitiveType()) return true;
if (getTypeGenericity(context, t) == Type::GENERIC) return false;
Copy link
Member

Choose a reason for hiding this comment

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

I suggest swapping this line with the previous, since something like integral might be considered a primitive type, but can't be default initialized

return kind() == QualifiedType::CONST_REF;
}
bool isRefMaybeConst() const {
return kind() == QualifiedType::REF_MAYBE_CONST;
Copy link
Member

Choose a reason for hiding this comment

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

IIRC @jabraham17 has removed ref-maybe-const formals from the production compiler. I don't recall if they have gone through a whole deprecation cycle. But we should probably remove this logic from dyno. Could you make an issue about it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How do I know if it's assigning from or assigning to?
bar(foo())
bar could have in intent -> assign to formal from foo()
bar could have out intent -> assign from formal to foo()
Copy link
Member

Choose a reason for hiding this comment

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

What was the solution to this conundrum in the end? Are we leaving it as future work or did you finesse the issue by assuming it's whatever would not have been a const-ness error?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As far as const checking goes, it seems like if foo() returns by const ref then it cannot be passed to bar in the first place. I get a const checking error earlier in the pass before the associated action is even checked.

I'm blanking here - is there another pattern I could test here to make sure out is doing the right thing?

Copy link
Member

Choose a reason for hiding this comment

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

I don't think further action is needed here right now & I'm happy with your answer to my question.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants