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

Specify private override errors #2283

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Changes from 5 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
279 changes: 118 additions & 161 deletions specification/dartLangSpec.tex
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
% - Clarify the conflicts between extension members and `Object` instance
% members.
% - Correct <partDeclaration> to include metadata.
% - Clarify errors involving private members that are brought together in a
% library where they are not accessible.
%
% 2.14
% - Add constraint on type of parameter which is covariant-by-declaration in
Expand Down Expand Up @@ -2516,11 +2518,13 @@ \section{Classes}
\rationale{%
We want different behavior for concrete classes and abstract classes.
If $A$ is intended to be abstract,
we want the static checker to warn about any attempt to instantiate $A$,
the static checker should report an error
if an attempt is made to instantiate $A$,
and we do not want the checker to complain about unimplemented methods in $A$.
In contrast, if $A$ is intended to be concrete,
the checker should warn about all unimplemented methods,
but allow clients to instantiate it freely.%
the checker should raise an error for all unimplemented methods,
but clients should be allowed to instantiate it.
Copy link
Member

Choose a reason for hiding this comment

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

Is this saying that there won't be an error reported at the instantiation point, only at the class declaration point?
If so, we don't usually say where errors are reported, we just say that "it is an error if ...." (here: "A non-
abstract class does not implement its own interface", or "A non-abstract class does not have a concrete member implementation for each member of its interface").

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 this says that when we have abstract class A{} any occurrences of A() will be flagged as an error, but it is not an error for A to have members in its interface that aren't implemented (with a correctly overriding signature), and the latter is an error for class A {}, but A() is allowed.

This would be the fundamental reason why we don't just say that being abstract is a computed property: If the class implements all methods then it's concrete, otherwise it is abstract. I believe that reasoning is still valid.

I think the location in the code where the error is reported is implied: When the error arises because of an instance creation A() then it will flag the location where that instance creation occurs.

I adjusted the wording a bit, I think it's more unambiguous now.

This motivates the use of an explicit modifier to identify abstract classes.%
eernstg marked this conversation as resolved.
Show resolved Hide resolved
}

\commentary{%
Expand All @@ -2532,6 +2536,12 @@ \section{Classes}
(\ref{interfaces}, \ref{superinterfaces}).%
}

\LMHash{}%
It is a compile-time error if a class $C$ has the interface $I$,
and $I$ has a member signature conflict
with respect to any name and library
(\ref{interfaceInheritanceAndOverriding}).

\LMHash{}%
When a class name appears as a type,
that name denotes the interface of the class.
Expand Down Expand Up @@ -2571,14 +2581,74 @@ \section{Classes}
\}
\end{dartCode}

\LMHash{}%
eernstg marked this conversation as resolved.
Show resolved Hide resolved
\BlindDefineSymbol{C, D, m}%
Consider a class $C$
and an instance member declaration $D$ in $C$, with member signature $m$
(\ref{interfaces}).
It is a compile-time error if $D$ overrides a declaration
% Note that $m'$ is accessible, due to the definition of `overrides'.
with member signature $m'$
from a direct superinterface of $C$
(\ref{interfaceInheritanceAndOverriding}),
unless $m$ is a correct member override of $m'$
eernstg marked this conversation as resolved.
Show resolved Hide resolved
(\ref{correctMemberOverrides}).

\commentary{%
This is not the only kind of conflict that may exist:
eernstg marked this conversation as resolved.
Show resolved Hide resolved
An instance member declaration $D$ may conflict with another declaration $D'$,
eernstg marked this conversation as resolved.
Show resolved Hide resolved
even in the case where they do not have the same name
or they are not the same kind of declaration.
E.g., $D$ could be an instance getter and $D'$ a static setter
eernstg marked this conversation as resolved.
Show resolved Hide resolved
(\ref{classMemberConflicts}).%
}

\LMHash{}%
For each parameter $p$ of $m$ where \COVARIANT{} is present,
it is a compile-time error if there exists
a direct or indirect superinterface of $C$ which has
an accessible method signature $m''$ with the same name as $m$,
eernstg marked this conversation as resolved.
Show resolved Hide resolved
such that $m''$ has a parameter $p''$ that corresponds to $p$
(\ref{covariantParameters}),
unless the type of $p$ is a subtype or a supertype of the type of $p''$.
eernstg marked this conversation as resolved.
Show resolved Hide resolved

\commentary{%
This means that
a parameter which is covariant-by-declaration can have a type
which is a supertype or a subtype of the type of
a corresponding parameter in a superinterface,
but the two types cannot be unrelated.
Note that this requirement must be satisfied
for each direct or indirect superinterface separately,
because that relationship is not transitive.%
}

\rationale{%
The superinterface may be the statically known type of the receiver,
so this means that we relax the potential typing relationship
between the statically known type of a parameter and the
type which is actually required at run time
to the subtype-or-supertype relationship,
rather than the strict supertype relationship
which applies to a parameter which is not covariant.
It should be noted that it is not statically known
at the call site whether any given parameter is covariant,
because the covariance could be introduced in
a proper subtype of the statically known type of the receiver.
We chose to give priority to flexibility rather than safety here,
because the whole point of covariant parameters is that developers
can make the choice to increase the flexibility
in a trade-off where some static type safety is lost.%
}


\subsection{Fully Implementing an Interface}
\LMLabel{fullyImplementingAnInterface}

% Note that rules here and in \ref{instanceMethods} overlap, but they are
% Note that rules here and in \ref{classes} overlap, but they are
eernstg marked this conversation as resolved.
Show resolved Hide resolved
% both needed: This section is concerned with concrete methods, including
% inherited ones, and \ref{instanceMethods} is concerned with instance
% members declared in $C$, including both concrete and abstract ones.
% inherited ones, and \ref{classes} is concerned with instance members
% declared in $C$, including both concrete and abstract ones.

\LMHash{}%
% The use of `concrete member' below may seem redundant, because a class
Expand Down Expand Up @@ -2697,7 +2767,7 @@ \subsection{Fully Implementing an Interface}
This ensures that an inherited method satisfies the same constraint
for each formal parameter which is covariant-by-declaration
as the constraint which is specified for a declaration in $C$
(\ref{instanceMethods}).%
(\ref{classes}).%
}


Expand All @@ -2714,66 +2784,6 @@ \subsection{Instance Methods}
and the instance methods inherited by $C$ from its superclass
(\ref{inheritanceAndOverriding}).

\LMHash{}%
\BlindDefineSymbol{C, D, m}%
Consider a class $C$
and an instance member declaration $D$ in $C$, with member signature $m$
(\ref{interfaces}).
It is a compile-time error if $D$ overrides a declaration
% Note that $m'$ is accessible, due to the definition of `overrides'.
with member signature $m'$
from a direct superinterface of $C$
(\ref{interfaceInheritanceAndOverriding}),
unless $m$ is a correct member override of $m'$
(\ref{correctMemberOverrides}).

\commentary{%
This is not the only kind of conflict that may exist:
An instance member declaration $D$ may conflict with another declaration $D'$,
even in the case where they do not have the same name
or they are not the same kind of declaration.
E.g., $D$ could be an instance getter and $D'$ a static setter
(\ref{classMemberConflicts}).%
}

\LMHash{}%
For each parameter $p$ of $m$ where \COVARIANT{} is present,
it is a compile-time error if there exists
a direct or indirect superinterface of $C$ which has
an accessible method signature $m''$ with the same name as $m$,
such that $m''$ has a parameter $p''$ that corresponds to $p$
(\ref{covariantParameters}),
unless the type of $p$ is a subtype or a supertype of the type of $p''$.

\commentary{%
This means that
a parameter which is covariant-by-declaration can have a type
which is a supertype or a subtype of the type of
a corresponding parameter in a superinterface,
but the two types cannot be unrelated.
Note that this requirement must be satisfied
for each direct or indirect superinterface separately,
because that relationship is not transitive.%
}

\rationale{%
The superinterface may be the statically known type of the receiver,
so this means that we relax the potential typing relationship
between the statically known type of a parameter and the
type which is actually required at run time
to the subtype-or-supertype relationship,
rather than the strict supertype relationship
which applies to a parameter which is not covariant.
It should be noted that it is not statically known
at the call site whether any given parameter is covariant,
because the covariance could be introduced in
a proper subtype of the statically known type of the receiver.
We chose to give priority to flexibility rather than safety here,
because the whole point of covariant parameters is that developers
can make the choice to increase the flexibility
in a trade-off where some static type safety is lost.%
}


\subsubsection{Operators}
\LMLabel{operators}
Expand Down Expand Up @@ -4564,7 +4574,7 @@ \subsubsection{Inheritance and Overriding}
Whether an override is legal or not is specified relative to
all direct superinterfaces, not just the interface of the superclass,
and that is described elsewhere
(\ref{instanceMethods}).
(\ref{classes}).
Static members never override anything,
but they may participate in some conflicts
involving declarations in superinterfaces
Expand Down Expand Up @@ -4906,16 +4916,13 @@ \section{Interfaces}
An interface has method, getter and setter signatures,
and a set of superinterfaces,
which are again interfaces.
Each interface is the implicit interface of a class,
in which case we call it a
\IndexCustom{class interface}{interface!class},
or a combination of several other interfaces,
in which case we call it a
\IndexCustom{combined interface}{interface!combined}.

\LMHash{}%
\BlindDefineSymbol{C, I}%
Let $C$ be a class.
The \Index{class interface} $I$ of $C$ is the interface that declares
The
\IndexCustom{class interface}{interface!class}
$I$ of $C$ is the interface that declares
a member signature derived from
each instance member declared by $C$.
The \Index{direct superinterfaces} of $I$ are the direct superinterfaces of $C$
Expand Down Expand Up @@ -4949,74 +4956,6 @@ \section{Interfaces}
$T$ is considered to have a method named \CALL{} with signature $m$,
such that the function type of $m$ is $T_0$.

\LMHash{}%
\BlindDefineSymbol{I, \List{I}{1}{k}}%
The \Index{combined interface} $I$ of a list of interfaces \List{I}{1}{k}
is the interface that declares the set of member signatures $M$,
where $M$ is determined as specified below.
The \Index{direct superinterfaces} of $I$ is the set \List{I}{1}{k}.

\LMHash{}%
Let $M_0$ be the set of all member signatures declared by \List{I}{1}{k}.
\DefineSymbol{M} is then the smallest set satisfying the following:

\begin{itemize}
\item For each name \id{} and library $L$ such that $M_0$ contains
a member signature named \id{} which is accessible to $L$,
let $m$ be the combined member signature named \id{}
from \List{I}{1}{k} with respect to $L$.
It is a compile-time error
if the computation of this combined member signature failed.
Otherwise, $M$ contains $m$.
\end{itemize}

\rationale{%
Interfaces must be able to contain inaccessible member signatures,
because they may be accessible from the interfaces associated with
declarations of subtypes.%
}

\commentary{%
For instance, class $C$ in library $L$ may declare a private member named
\code{\_foo},
a class $D$ in a different library $L_2$ may extend $C$,
and a class $E$ in library $L$ may extend $D$;
$E$ may then declare a member that overrides \code{\_foo} from $C$,
and that override relation must be checked based on the interface of $D$.
So we cannot allow the interface of $D$
to ``forget'' inaccessible members like \code{\_foo}.

For conflicts the situation is even more demanding:
Classes $C_1$ and $C_2$ in library $L$ may declare private members
\code{String \_foo(int i)} and \code{int get \_foo},
and a subtype $D_{12}$ in a different library $L_2$ may have
an \IMPLEMENTS{} clause listing both $C_1$ and $C_2$.
In that case we must report a conflict even though the conflicting
declarations are not accessible to $L_2$,
because those member signatures are then noSuchMethod forwarded
(\ref{theMethodNoSuchMethod}),
and an invocation of \code{\_foo} on an instance of $D$ in $L$
must return an `int` according to the first member signature,
and it must return a function object according to the second one,
and an invocation of \code{\_foo(42)}
must return a \code{String} with the first member signature, and it must fail
(at compile time or, for a dynamic invocation, run time) with the second.%
}

\rationale{%
It may not be possible to satisfy such constraints simultaneously,
and it will inevitably be a complex semantics,
so we have chosen to make it an error.
It is unfortunate that the addition of a private declaration
in one library may break existing code in a different library.
But it should be noted that the conflicts can be detected locally
in the library where the private declarations exist,
because they only arise for private members with
the same name and incompatible signatures.
Renaming that private member to anything not used in that library
will eliminate the conflict and will not break any clients.%
}


\subsection{Combined Member Signatures}
\LMLabel{combinedMemberSignatures}
Expand Down Expand Up @@ -5062,7 +5001,7 @@ \subsection{Combined Member Signatures}
\FunctionTypeSimple{\DYNAMIC}{} and \FunctionTypeSimple{\code{Object}}{}
are not equal, even though they are subtypes of each other.
We need this distinction because management of top type discrepancies is
one of the purposes of computing a combined interface.%
one of the purposes of computing combined member signatures.%
}

\LMHash{}%
Expand Down Expand Up @@ -5282,26 +5221,44 @@ \subsubsection{Inheritance and Overriding}
\end{itemize}

\LMHash{}%
Let $I$ be the interface of a class $C$ declared in library $L$.
$I$ \Index{inherits} all members of $\inherited{I, L}$
and $I$ \Index{overrides} $m'$ if $m' \in \overrides{I, L}$.
Let $I$ be the interface of a class $C$ declared in a library $L$,
and let $K$ be a library.
We say that the set of members that $I$
eernstg marked this conversation as resolved.
Show resolved Hide resolved
\IndexCustom{inherits with respect to $K$}{interface!inherits}
is $\inherited{I, K}$,
eernstg marked this conversation as resolved.
Show resolved Hide resolved
and the set of members that $I$
\IndexCustom{overrides}{interface!overrides}
is $\overrides{I, L}$.

\commentary{%
Note that an interface can inherit members that are inaccessible
(because they are private to a different library),
but it can only override members that are accessible.%
}

\LMHash{}%
All the compile-time errors pertaining to the overriding of instance members
given in section~\ref{classes} hold for overriding between interfaces as well.
We say that $I$
\IndexCustom{has a member}{interface!has a member}
$m$ if $I$ inherits $m$ with respect to any library,
or $I$ overrides $m$.

\LMHash{}%
If the above rule would cause multiple member signatures
with the same name \id{} to be inherited then
exactly one member is inherited, namely
the combined member signature named \id,
from the direct superinterfaces
% This is well-defined because $I$ is a class interface.
in the textual order that they are declared,
with respect to $L$
(\ref{combinedMemberSignatures}).
It is a compile-time error
if the computation of said combined member signature fails.
\BlindDefineSymbol{I, K, n, m}%
Let $I$ be an interface, $K$ a library, and
$n$ a name such that a member $m$ with the name $n$
is inherited by $I$ with respect to $K$.
\commentary{%
Note that there may be more than one such member.%
}
The
\IndexCustom{member signature}{interface!member signature}
of $I$ for $n$ with respect to $K$ is
the combined member signature for $n$
of the direct superinterfaces of $I$ with respect to $K$.
In the case where the computation of the combined member signature fails,
we say that $I$ has a
\IndexCustom{member signature conflict}{interface!member signature conflict}
with respect to $n$ and $K$.


\subsubsection{Correct Member Overrides}
Expand Down Expand Up @@ -5351,7 +5308,7 @@ \subsubsection{Correct Member Overrides}
must have a type which satisfies one more requirement,
relative to the corresponding parameters in all superinterfaces,
both direct and indirect
(\ref{instanceMethods}).
(\ref{classes}).
We cannot make that requirement a part of the notion of correct overrides,
because correct overrides are only concerned with
the relation to a single superinterface.%
Expand Down