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

(Closes #2642, initial step towards #2643) Implement extends(type), procedure in derived type, class in declarations. #2644

Open
wants to merge 28 commits into
base: master
Choose a base branch
from

Conversation

JulienRemy
Copy link
Collaborator

This adds the following object- and inheritance-oriented features:

  • an extends attribute in StructureType for inheritance of derived types, which avoids relying on UnsupportedFortranType and its declaration: str attribute,
  • a procedure_components attribute in StructureType for procedures in a contains block of a derived type, including visibility and initial value,
  • support for class (versus type) in routine declarations, by introducing a boolean attribute in DataTypeSymbol.

Note that as this makes some derived types supported there is for now a workaround using FortranWriter in both GOceanKernelMetadata and LFRicKernelMetadata, which rely on UnsupportedFortranType().declaration and fparser, see #2643.

@JulienRemy JulienRemy requested review from sergisiso and arporter July 2, 2024 10:48
@JulienRemy JulienRemy marked this pull request as draft July 2, 2024 10:50
@JulienRemy
Copy link
Collaborator Author

This will require me to edit the documentation, including (but maybe not limited to) https://psyclone-dev.readthedocs.io/en/latest/psyir_symbols.html#datatypes

@JulienRemy
Copy link
Collaborator Author

@arporter @sergisiso This works fine in practice on our project.
I’ll need to add some more specific tests, see what’s up with the master branch conflicts (Aidan’s routines things?) and refactor this thing for sure so it’s going to stay a draft until next week at least.
But if you have time for a broad overview at some point I’m definitely interested in it :) Don’t bother with the details, but if something doesn’t agree with what you’d want in there, feel very free to point it out and I’ll rework it. Most especially: I’m not so sure the way I’m getting the fparser attributes in the frontend is very clean at times.

@JulienRemy JulienRemy marked this pull request as ready for review November 28, 2024 11:05
@stfc stfc deleted a comment from codecov bot Nov 29, 2024
Copy link

codecov bot commented Nov 29, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 99.88%. Comparing base (8736ae3) to head (cbc5857).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2644      +/-   ##
==========================================
- Coverage   99.88%   99.88%   -0.01%     
==========================================
  Files         357      357              
  Lines       49823    50214     +391     
==========================================
+ Hits        49767    50156     +389     
- Misses         56       58       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@JulienRemy
Copy link
Collaborator Author

@sergisiso @arporter Two remaining lines still to be tested for codecov (I’ll do this on Monday) but this is otherwise ready for review.
It might be nice to fire the integration tests to be sure it breaks nothing in lfric due to the metadata extraction workaround. I’m arguably going a bit blind on that front.
Also interested on your opinion on my having to xfail one test in due to added support: src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint_harness.py There’s probably some better way to deal with the PSyclone error on these two routines not being imported in the TL source code.

@arporter
Copy link
Member

arporter commented Dec 2, 2024

Thanks @JulienRemy. I've set the integration tests going...

@arporter
Copy link
Member

arporter commented Dec 2, 2024

Unfortunately both the LFRic integration tests and the compilation tests failed. You should be able to reproduce the latter by running pytest with the --compile flag. The error is:
image
One of the tests that failed is:

tests/psyir/frontend/fparser2_select_type_test.py

The LFRic integration tests failed when linking the LFRic executable:
image

Copy link
Member

@arporter arporter left a comment

Choose a reason for hiding this comment

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

Many thanks for this Julien, it's a big change, especially since it impacts the metadata handling. In general I think it's good although there are a few things to discuss and I can't remember all of them now as it's taken me several days to get this first review done. However, it's all in the comments so we can work through them in the usual way.

src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py Outdated Show resolved Hide resolved
src/psyclone/domain/gocean/kernel/psyir.py Show resolved Hide resolved
src/psyclone/psyGen.py Outdated Show resolved Hide resolved
src/psyclone/psyGen.py Show resolved Hide resolved
" use kernel_mod, only : parent_type\n"
" type, extends(parent_type) :: test_type\n"
" integer :: i = 1\n"
" contains\n"
Copy link
Member

Choose a reason for hiding this comment

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

Please could you extend this test so that the type contains a private statement. The visibility of the test_code procedure should then become private.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done, it works :)

Copy link
Member

Choose a reason for hiding this comment

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

Not quite and unfortunately I think it's broken (see below) ! :-)

src/psyclone/tests/psyir/symbols/datatype_test.py Outdated Show resolved Hide resolved
src/psyclone/tests/psyir/symbols/datatype_test.py Outdated Show resolved Hide resolved
@JulienRemy
Copy link
Collaborator Author

Thanks for your review @arporter :)
I've pushed my edits and detailed some answers above as well.

if type(extends_symbol) is Symbol:
extends_symbol.specialise(DataTypeSymbol)
extends_symbol.datatype = StructureType()
# If it is not in the symbol table, create a new
Copy link
Member

Choose a reason for hiding this comment

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

Please move this comment into the else: block as that's what it applies to. If we are creating a Symbol then it should be added to a SymbolTable. Given that the lookup hasn't found it, it will have to have an Unresolved interface. That means we know it exists somewhere but we don't know how it is brought into scope.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right. This is done!

src/psyclone/domain/gocean/kernel/psyir.py Show resolved Hide resolved
@@ -3268,6 +3339,10 @@ def _add_target_attribute(var_name, table):
(e.g. a routine argument or an imported symbol).

'''
# pylint: disable=import-outside-toplevel
Copy link
Member

Choose a reason for hiding this comment

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

Sorry to be a pain but please could you move the pylint disable down too.. In fact, could you also change it to be # pylint: disable-next=... as that's even tighter.

:param tsymbol: the DataTypeSymbol representing the derived-type.
:type tsymbol: :py:class:`psyclone.psyir.symbols.DataTypeSymbol`

:returns: None
Copy link
Member

Choose a reason for hiding this comment

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

No need for a :returns: if it doesn't return anything :-)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

'''

contains_blocks = walk(decl, Fortran2003.Type_Bound_Procedure_Part)
if contains_blocks:
Copy link
Member

Choose a reason for hiding this comment

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

To reduce subsequent indentation, I suggest changing this to:

if not contains_blocks:
    return

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Indeed :)

# `testkern_code` in `procedure :: code => testkern_code`
if procedure.items[4] is not None:
initial_value_name = procedure.items[4].string
initial_value_symbol = parent.symbol_table.lookup(
Copy link
Member

Choose a reason for hiding this comment

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

I think find_or_create (https://psyclone-ref.readthedocs.io/en/latest/_static/html/classpsyclone_1_1psyir_1_1symbols_1_1symbol__table_1_1SymbolTable.html#ada706dd884505330c3d24b9b9a738042) would simplify things here. You can pass the symboltype and data_type that you want (if it doesn't exist) as arguments.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done, it's indeed clearer.

src/psyclone/psyir/frontend/fparser2.py Outdated Show resolved Hide resolved
:raises TypeError: if the new value is not a Reference to a
RoutineSymbol.

:returns: None
Copy link
Member

Choose a reason for hiding this comment

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

Please remove this line.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

new_datatype,
procedure_component.visibility,
new_value)
return
Copy link
Member

Choose a reason for hiding this comment

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

Can a datatype contain more than one procedure with the same initial value? I would guess that it can. Same goes for the early return at L1216. Please also add a test for this case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agreed about the early returns, there could be multiple procedure, ... => old_name to replace in there. They're removed and I've added a test with multiple procedure components (supported and not).

or procedure_component.initial_value.name.lower()
!= old_value_name.lower()):
continue
self._procedure_components[name] = self.ComponentType(
Copy link
Member

Choose a reason for hiding this comment

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

If we made ComponentType mutable (i.e. not frozen) this would be a bit simpler - in case that swings the argument in your other PR that @sergisiso is reviewing.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I guess keeping it frozen makes sense, otherwise we get a Symbol-equivalent that's mutable? Feels a bit dangerous.

''' Check that the CodedKern rename method renames the kernel in the PSyIR
tree. '''
# pylint: disable=protected-access, too-many-statements
_, invoke_info = parse(os.path.join(BASE_PATH, "1_single_invoke.f90"),
Copy link
Member

Choose a reason for hiding this comment

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

I'll give you that but they are old tests :-) It's:

psy, invoke_info = get_invoke("1_single_invoke.f90", "lfric", dist_mem=False)

assert isinstance(sym.datatype, DataTypeSymbol)
assert sym.datatype.is_class is True
assert sym.name == "urgh"
assert sym.datatype.name == "*"
Copy link
Member

Choose a reason for hiding this comment

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

As discussed in the implementation, I think this should either be UnsupportedFortranType (the easiest thing to do) or an UnresolvedFortranType with an is_class==True attribute.

" end type test_type\n" in result)

# private type that contains procedures
# the `test_code` procedure should thus become private
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 this is correct. You've put the private attribute on the declaration of test_type so test_type will be private - this says nothing about the visibility of the components within it. What I meant was:

type my_type
  private
  contains
     procedure :: this_should_be_private
end type

Copy link
Collaborator Author

@JulienRemy JulienRemy Dec 19, 2024

Choose a reason for hiding this comment

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

Oops, I had misunderstood. Indeed, components should not inherit the derived type visibility and my implementation was plain wrong. Thanks for spotting this!

I checked the standard and implemented visibility for procedure components using a "standalone" private in the contains part.
However, note that private statements in the data components and procedure components parts are independent (see e.g. NOTE 4.44 p.74 of https://dci.dci-gitlab.cines.fr/webextranet/_downloads/91b30eb00b39f6896fae528e5d0d66fc/Fortran_2003_N1601.pdf) so that your code above would not modify the visibility of this_should_be_private whereas this would:

type my_type
  contains
     private
     procedure :: this_should_be_private
end type

I've fixed this and added tests where the standalone private statement either does or does not impact the visibility of a procedure and of a data component.

" type, extends(parent_type), private :: test_type\n"
" integer, public :: i = 1\n"
" contains\n"
" procedure, private :: test_code\n"
Copy link
Member

Choose a reason for hiding this comment

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

This should remain public I think? Worth keeping this test in addition to one where you add a private statement inside the type.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're totally right, see above.

" procedure (my_interface) :: test_code\n"
" end type test_type\n" in result)

# type that contains procedures with pass, nopass and deferred
Copy link
Member

Choose a reason for hiding this comment

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

As with other tests elsewhere, please could you break this one up into separate tests, as indicated by the comments that you have already.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done!

" use kernel_mod, only : parent_type\n"
" type, extends(parent_type) :: test_type\n"
" integer :: i = 1\n"
" contains\n"
Copy link
Member

Choose a reason for hiding this comment

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

Not quite and unfortunately I think it's broken (see below) ! :-)

src/psyclone/tests/psyir/symbols/datatype_test.py Outdated Show resolved Hide resolved
Copy link
Member

@arporter arporter left a comment

Choose a reason for hiding this comment

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

Looking pretty good now, thanks Julien.
We will need to update the frontend a little to handle class(*) - I've suggested a couple of options.
Also, I think the visibility of members of a type needs a little work.
I'll set the integration tests running now in case that shows any other issues.

@JulienRemy
Copy link
Collaborator Author

@arporter All edits except for those about class(*) have been done, pending a decision about these.

@arporter
Copy link
Member

@hiker and @sergisiso - we could do with your opinion on how to handle declarations of type class(*) - these are "unlimited polymorphic type". My thought is to have these as UnresolvedType and extend that type with an is_class property. That's perhaps going to be non intuitive though as it will mean we then need to generate a declaration for such a Symbol but only if is_class is True (and also, the meaning of UnresolvedType is currently that we can't resolve it but could in principle). We may therefore be better off adding an entirely new type, possibly DeferredType? What do you think?

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