Skip to content

Add revised protected components & types proposal #182

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 7 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
363 changes: 363 additions & 0 deletions proposals/protected-components.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,363 @@
To: J3 J3/XX-XXX
From: Zach Jibben
Subject: Protected components & types: specifications, & syntax
Date: 2020-October-1
Reference: 20-121 20-106 19-214r1 19-161 19-135r1 18-265


1. Introduction
===============

This paper contains the formal specifications & syntax for protected
components of a type, and protected types. This supersedes the
specifications outlined by previous papers, as subgroup discussion revealed
a more flexible feature set was needed to meet competing user requirements.

Previous protected-components specifications fell into one of two camps.
Papers 18-265 and 20-106 envisioned components inaccessible for *direct*
modification outside the module in which the parent type was defined, but
did allow a type containing a protected potential subobject to appear in a
variable-definition context. These papers propose an access specifier
roughly in-between PUBLIC and PRIVATE, by effectively prohibiting the name
of a protected component from appearing in a variable-definition context,
not protecting the variable itself. Other papers, 19-135r1, 19-161,
19-214r1, and 20-121 offered stronger protection, protecting variables
themselves from being modified by virtually any means outside the module
where that component was defined.

This paper aims to satisfy both parties by teasing apart the competing
goals into two separate features: protected components, and protected
types. Protected components provide an access specification allowing code
outside the module defining the type to read, but not *directly* modify
components. These components will not impose any restrictions on the type
containing them, beyond anything that might be imposed by the analogous
PRIVATE or PUBLIC access specifiers. Protected types may be used to protect
the data from appearing in variable-definition contexts.

Although this is a formal syntax paper, the syntax will be defined by prose
and by example, not by BNF, to aid comprehension.


2. Use Cases
============

The use cases for both features have been presented in previous papers
(protected components 18-265, 20-106, 19-214r1; protected types 19-135r1).
Here are presented two possible use-cases. For more, refer to the above
papers.


2.1 Protected Components

Protected components are useful in any situation where a user would want a
derived type to expose a large dataset for reading, but does not want the
client to modify the data except through provided procedures. They may also
want the client to be able to store copies of these objects or deallocate
them, while still controlling their internal consistency. Furthermore, a
"getter" routine would require an expensive copy of the data, thus is
suboptimal. Protected components allow the programmer to design a derived
type API which reflects the intended use of certain objects -- exposing
components without also giving the ability to modify them directly.

For example, a CFD application's mesh may contain a large connectivity
dataset:

type :: mesh
integer, allocatable, protected :: nbr(:)
contains
procedure :: init
end type mesh

A client must be able to create and initialize this mesh object through
provided procedures. They might need copies to preserve some history. But
the client absolutely must not be able to modify the nbr component
directly. To summarize:

subroutine ex1()
type(mesh) :: m ! local variables allowed
type(mesh), allocatable :: history(:) ! local allocatables allowed

call m%init() ! modifying through module routines allowed
print *, m%nbr ! reading protected components allowed
allocate(history(1)) ! local allocation allowed
history(1) = m ! intrinsic assignment allowed
call read(m%nbr) ! passing protected component as intent(in) allowed
call new_mesh(m) ! passing object as intent(out) or intent(inout)
! allowed

m%nbr(1) = huge(1) ! FAIL: modifying protected component not allowed
call change(m%nbr(1)) ! FAIL: intent(out) and intent(inout) not
! allowed on protected components
m = mesh(nbr = [huge(1)]) ! FAIL: setting protected component
! via structure constructor not allowed
allocate(m%nbr(1)) ! FAIL
deallocate(m%nbr) ! FAIL

! automatic deallocation & finalization allowed
! even without a user-defined finalizer
end subroutine ex1

2.2 Protected Types

Type protection disables intrinsic assignment, allocation, deallocation,
and finalization outside the module where that type is defined. It does not
impose any restrictions on modifying components -- that can be done
independently with component protection.

This provides compile-time feedback when a client attempts to use a
protected derived type in a way the author intends to disallow, and gives
the author the ability to provide custom alternatives to only those
features they intend to allow.

One example is a linked list, in which one may wish to disallow intrinsic
assignment entirely. Otherwise, a client could inadvertently produce a
shallow-copy.


3. Specifications
=================

To help keep a record of how specs have changed and to aid discussion,
specification labels are preserved from papers 20-121 and 19-214r1.


3.1 Protected Components

B. The name of a protected component shall not appear in a
variable-definition context, except within the module wherein its type
is defined.

G. A protected component or a subobject of a protected component can only
be argument associated with an INTENT(IN) dummy, even if the referenced
procedure is in the module in which the type is defined.

Choose a reason for hiding this comment

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

I believe this is implied by B, but nothing wrong with being explicit.


H. A protected component or subobject of a protected component cannot be
the target in a pointer assignment outside the module, as that would
lose the protection.
- Here I think it would be valuable to somehow expose an immutable
reference, which could tie into the const-pointer proposals.

Choose a reason for hiding this comment

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

Good eye, forward thinking. I had a similar thought, but didn't see a reason to bring it up and complicate matters. Worth keeping in mind though.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks. We'll see what comes of this, but given there's already work on the other feature it would be cool to get both of these working together.

Copy link
Contributor

Choose a reason for hiding this comment

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

I absolutely agree, a read-only reference would be very handy here.


I. A protected component or subobject of a protected component cannot be
explicitly allocated or deallocated except within the module in which
the type is defined.
- Otherwise it's too easy to bypass the protection.
- Allocating/deallocating a type containing a protected component is
still permitted, provided it's not subject to some other rule.
- Automatic deallocation on scope exit will occur as it would for any
unprotected component.

J1. A dummy variable of a type with a protected component can be INTENT(IN)
or INTENT(INOUT) or INTENT(OUT) in any procedure anywhere, unless it's
subject to some other rule.

L1. No intrinsic assignment to a protected component or subobject thereof,
outside the module in which the protected component is defined. (cf.
19-135r1)
- This does not prohibit intrinsic assignment to a type containing a
protected component. It is only meant to prevent the protected
component from being modified by name.
Comment on lines +153 to +158
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't this already covered by B anyway?

Copy link
Member Author

Choose a reason for hiding this comment

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

I believe it is covered by B. The original paper I built off had both (19-214r1), and I can't say I know why. I'm taking a "change as little as possible" approach compared to what they had.


M1. A function defined outside the module may have a result variable of a
type with a protected component.

N. A structure constructor outside the module in which the type is defined
is allowed if and only if no value is supplied for any protected
component (otherwise this would subvert the module's control over what
values are acceptable in the protected component).
- This describes the same user-facing behavior for PROTECTED and PRIVATE
components. However, the logic in the standard must be different.
C7102 and 7.5.4.8p2 prevent a structure constructor from specifying
a private component by restricting access to the name. For a PROTECTED
component, the name is accessible, yet we still prohibit setting the
value of such a component outside the module where the parent type is
defined.

P1. Protected components are inherited through type extension.
- In this regard, PROTECTED is like PUBLIC, not PRIVATE.
Comment on lines +175 to +176
Copy link
Contributor

Choose a reason for hiding this comment

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

In all other rules, PROTECTED behaves as PRIVATE, except for its readable behavior. Should it not be the case for this rule too?

Copy link
Member Author

Choose a reason for hiding this comment

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

My reasoning here is PROTECTED components are part of an API, something a client can see and read. PRIVATE components are completely invisible outside the type, hence it's safe not to inherit them. But if a client has a class(parent) with a protected component, the dynamic type type(child) ought to share the parent's API.


R. Type-wide default protected status, like PRIVATE, is provided. Default
is PUBLIC, unchanged from the current standard. PROTECTED statement
changes the default. Attributes in a component definition stmt confirm
or override the default.

U. SEQUENCE and BIND(C) types shall not have protected components.
- This includes any level of component selection, because non-SEQUENCE
types are not allowed in SEQUENCE, and similarly BIND(C)).

AA. A component may be PROTECTED or PRIVATE or PUBLIC, but not a
combination of more than one of these attributes.


3.2 Protected Types
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 protected type be extended? If yes, would the extending type also be protected? It seems to me that it would have to be, otherwise it would be easy to "unprotect" a protected type. This should be spelled out in the spec.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, I see P2.


A. A variable whose declared type is protected shall not appear in a
variable-definition context, except within the module wherein its type
is defined.

C. A local variable whose declared type is protected is allowed outside the
module in which the type is defined.
- If some action is needed when the variable goes out of scope (and is
thus destroyed unless it has the SAVE attribute) its type should have
a final procedure.

D. A local pointer of a protected type is allowed outside the module in
which the type is defined.
- Otherwise use case 2.1 in 19-135r1 is not satisfied.

E. Deallocating an object with a protected declared type outside the module
in which the type is defined is prohibited.
- Otherwise for pointer, use case 2.1 in 19-135r1 is not satisfied.
- Allowing it for remote/dummy allocatable would prohibit an allocatable
variable in the module where the type is defined to be PUBLIC.
- Allowing for local allocatable breaks rule (A) about not appearing
in a variable definition context.

F. Allocating an object with a protected declared type outside the module
in which the type is defined is prohibited.
- Pointer would almost certainly leak memory.
- Allowing for remote/dummy allocatable would have same problems as (E).
- Allowing for local allocatable would have same problems as (E).

J2. A dummy variable of a protected type can be INTENT(IN) or INTENT(INOUT)
in any procedure anywhere. A dummy variable of such a type shall not
have INTENT(OUT) or unspecified intent except within the module in
which the type is defined.

K. Can modify (or allocate/deallocate) the *components* of a protected type
anywhere, provided the component is not subject to some other rule
(e.g., a protected component, or the component is itself of protected
type).
- This is a point for discussion. Type protection here would effectively
disable intrinsic assignment, allocation, deallocation, and
finalization outside the module where it's defined, but would not
impose any restrictions on the components. Component protection
(above) would be used to enforce both.
- This allows for the use case 2.3 presented in 19-135r1.

L2. Polymorphic allocatable assignment of a parent type without a protected
attribute is permitted even when the dynamic type has a protected
attribute.
- This presents a loophole which may allow a programmer to write to a
protected type outside the module in which it is defined. However,
subgroup did not like the alternative of disallowing the protected
attribute in extensions of unprotected parent types.

L3. No intrinsic assignment to an object whose declared type is protected,
or that has an ultimate component of protected type, outside the module
in which the protected component is defined. (cf. 19-135r1)

M2. A function defined outside the module may have a result variable of a
protected type.
- This is same as ordinary local variables (case A).
- The function result shall not be a pointer.
- The function result will (hopefully) be finalised after its use.

N2. A structure constructor outside the module in which the type is defined
is not allowed.

O. A type may have a component of protected type, and thereby inherits the
protected type attribute.
- The protection rules apply to types with "potential subobject
components of protected type" not just types with immediate components
of protected type. See E, F, L3.

P2. Extension of a protected type is permitted.

Q. An extension type may have a protected attribute even if the parent type
does not have a protected attribute. Requiring the parent type to have
be protected is too restricted for many uses.

S. An object with a protected declared type is prohibited to be the target
of an unlimited polymorphic pointer.
- This rule applies even inside the module, even if the polymorphic
pointer is private, because its target might thereafter become
associated with a public polymorphic pointer.

T. If an actual argument with a protected declared type is associated with
an unlimited polymorphic dummy, the dummy shall have INTENT(IN) or
INTENT(INOUT) and shall not have TARGET (or POINTER, which implies
TARGET).
- If the dummy has TARGET it might become associated with an unlimited
polymorphic pointer.
- If the dummy has POINTER, its target might eventually become
associated with an unlimited polymorphic pointer (see P.)


4. Syntax
=========

4.1 Protected Components

One may add the PROTECTED attribute to component definitions.

type :: foo
real, protected :: a
real, private :: b
real, public :: c
end type foo

The requirement R allows a type-wide PROTECTED status, which may be
overridden for an individual component using another access specifier.

type :: foo
protected
real, private :: a
real :: b
end type foo


4.2 Protected Types

One may add the PROTECTED attribute to type definitions. One may
independently control the access specifier of each component.

type, protected :: foo
real, public :: a
real, private :: b
real, protected :: c
end type foo


5. Comments
===========

5.1 Alternative keywords

The PROTECTED keyword meaning different things in these contexts may be
confusing. It's worth considering different keywords (e.g.,
readonly/visible components, persistent/restricted/controlled types, etc).

type, protected :: bar
protected
type(foo), protected :: a
end type bar


5.2 Is an unprotected type with only protected components the same as a
protected type?

A: No, an unprotected type with all protected components may be copied via
intrinsic assignment, allocated, deallocated, and automatically deallocated
outside the module wherein that type is defined. A protected type disables
all of these "type-level" definition contexts, and thus is more
restrictive.

This distinction is the motivation for having both protected components and
protected types. In some cases a user may want to protect data in a type
from any outside tampering, in which case a protected type is warranted. In
other cases, the user wants to ensure internal consistency in a derived
type, but doesn't want to prevent the user from creating or destroying the
type altogether. A user might also want to avoid expensive copies in getter
procedures, which would be needed for private components. See the use-cases
above.


5.3 Is a public component of a protected type any different from a
protected component of a protected type?

A: The answer here is yes. Specification K allows modification of the
components of a protected type; i.e. the protection of a type really only
restricts what can be done to the type as a whole. This is up for
discussion, though, and could be changed.

===END===