-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Explorer: Mixin phase 1 #2069
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
Explorer: Mixin phase 1 #2069
Conversation
Also removed an unnecessary check that would have already been handled by the parser.
(Commenting just on the PR description so far.) Leads-question issue #1000 is also relevant. Looks like you are taking the "data fields" approach, which I think is aligned with the thinking at the beginning of the year. Looking through the past discussions, I'm wondering what position is being taken on these design questions (as a plan):
My main concern is that it looks to me like some changes are needed to the current approach to support things we expect to need. Right now it looks like all members are injected, which is going to make it hard to support the intrusive list use case (and evolution). And the lack of name when including a mixin in a type is going to make adding data members tricky unless you have some other plan. There are other use cases I expect we need to address, like injecting implementations of virtual methods defined in a base of the containing class, mixins extending base types, and so on, but that looks independent of what is in the current PR. The "Duality of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've only reviewed the test cases, not the code.
__mix M2; | ||
__mix M3; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also:
__mixin M1 {
fn F1[me: Self](x: Self) -> Self {
return x;
}
}
__mixin M2 {
fn F2() {
}
__mix M1;
}
__mixin M3 {
__mix M1;
__mix M2;
}
Also:
__mixin M1 {
fn F1[me: Self](x: Self) -> Self {
return x;
}
}
__mixin M2 {
fn F2() {
}
__mix M1;
}
class C {
__mix M1;
__mix M2;
}
Are these constructs only a problem if they introduce name conflicts? What happens if the mixins have no members that conflict? Example:
__mixin M1 {
}
__mixin M2 {
__mix M1;
__mix M1;
}
Also:
__mixin M1 {
}
class C {
__mix M1;
__mix M1;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, all these examples would work because there's no name conflicts. We could add a more thorough check in the future. I'll try to address this in the proposal.
return x; | ||
} | ||
__mix M1; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if M1
doesn't have any members to conflict (or later they are all marked external
or something)? Should this be an error or allowed? It is definitely a problem if it has any members that take space.
__mixin M1 {
__mix M1;
}
fn F1[me: Self](x: Self) -> Self{ | ||
return x; | ||
} | ||
__mix M1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't M1
incomplete at this point? We haven't seen the closing }
of its definition yet. I guess that opens the question about whether you can mix a mixin that has only a forward declaration at the moment.
// Here Self which is both the input and output type is a type variable | ||
fn F[me: Self](x: Self) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, this is counter to my expectation for Self
. I'd only expect this test to work with T
of for T:! Type
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've had trouble understanding the purpose of for T
. Perhaps you could explain?
What is your expectation for Self
?
The meaning that we're currently using for Self
inside a mixin is that it is a placeholder for the class type that the mixin will become a part of.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My expectation for Self
is that it represents the mixin object, and T
would represent the containing type.
explorer/testdata/mixin/use-mixin-method-in-class-method.carbon
Outdated
Show resolved
Hide resolved
Hi Josh, These are all good questions. The main thing to realize is that the current PR just represents a baby step of exploration on mixins and does not represent any kind of commitment to particular design decisions. We're mainly figuring out the various mechanisms and algorithms that are needed in the type checker and interpreter. Regarding the data fields vs. base class approaches... the current PR is closer to what you call the base class approach, but like I said, that's not set in stone. Regarding name clashes... the current PR has zero support for resolving them, but of course we will need to address that. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great first step Darshal. Let's keep exploring!
Co-authored-by: josh11b <[email protected]>
Co-authored-by: josh11b <[email protected]>
Overview
This PR contains an initial implementation of a mixin language feature inspired
from other programming languages like Swift. This implementation is used to
flesh out the details of the mixin proposal which will be coming out in the
future. The ideas behind this PR and the mixin proposal that we are working on
are based off of this document which summarizes the outcome of the open
discussions about mixins with some of the leads. The minutes of the mixin
related open discussions can be found here (in the January discussions) and here
(in the December discussions).
Class inheritance in OOP allows for both code reuse, and subtyping which in turn
allows polymorphism. Currently, interfaces allow for polymorphism while mixins
will be a way to allow code reuse in Carbon. Mixins will also be a way of having
multiple inheritance since we can mix multiple mixins into a class.
A basic example that demonstrates code reuse using mixins is shown below. The
example demonstrates how the
Operations
mixin’s member methodSquare
can bereused in both the
Point
andComplex
classes using the mix declaration whichis a member of both classes.
The “__” prefix before the “
mix
” and “mixin
” keywords indicate that thesefeatures are experimental, and will be finalized when the proposal for mixins is
ready.
Implementation concepts
What does it mean to mix a mixin into a class/mixin?
A mixin is mixed into a class or mixin declaration by adding a mix declaration
as a member. A mix declaration collects all the members of the mixin mentioned
in the mix declaration and conceptually merges it into the list of members of
the enclosing class/mixin.
Duality of
Self
in a mixinThe
Self
in an interface is a type variable, and theSelf
of a mixin needsto also behave like a type variable because when a mixin is mixed into a class,
the
Self
type variable needs to behave like the enclosing class type. So weneed to substitute the
Self
type variable with the type of the enclosingclass. In the
Main
function of the above example, the type of theSquare
method accessed through the
p
object of typePoint
becomesfn Square[me:Point](x:i32) -> i32
after the substitution ofSelf
type variable.However, it should also be possible to access other mixin members in a mixin
member method body through the
Self
type. In this way theSelf
of a mixinshould behave like the
Self
in a class where it’s just an alias to the type ofthe declaration. Thus, the
Self
of a mixin needs to behave as both theSelf
type in a class and the
Self
type in an interface.In this PR, the
Self
in a mixin is aGenericBinding
so that it behaves likea type variable. As a result, it doesn’t behave like the
Self
in a class andwithin a mixin member method we can’t access other mixin members through “me”
which has the type
Self
. In the final version, theSelf
in a mixin will beimplemented as new class that has both the properties of the Self in classes and
interfaces.
Summary of changes
New declaration classes for mixin and mix declarations have been added.
MixinDeclaration
is very similar toInterfaceDeclaration
. AMixDeclaration
holds an expression that will eventually evaluate into a
MixinPseudoType
(seenext paragraph). During type checking, a pointer to the evaluated
MixinPseudoType
is assigned to theMixDeclaration
field namedmixin_value_
.The
MixinPseudoType
class has been created which is very similar toInterfaceType
. This class is the value associated with aMixinDeclaration
.Similarly, the
TypeOfMixinPseudoType
has also been created which is the typeassigned to a
MixinDeclaration
. These “values” are not allowed to be used in atype expression.
The new
FindMixedMethodAndType
function does the substitution ofSelf
with aconcrete type (as explained in the “duality of
Self
” section above) while typechecking class member access expressions.
The
FindFunction
method ofNominalClassType
has been modified to recursivelysearch for the function by following the mixins mentioned in
MixDeclaration
members.
A
collected_members_
field has been added to theTypeChecker
class thatstores all the direct and indirect members of all class and mixin declarations.
Indirect members are those members which have been exported by a mixin through a
mix declaration.
FindCollectedMembers
andCollectMember
are helper methodsthat retrieve members and add a new member for a particular class/mixin
declaration, respectively. When we have mix declarations as members, we traverse
a graph of
MixinDeclaration
nodes connected throughMixDeclaration
membersin order to fetch all indirect members of a class/mixin. This list of indirect
members along with direct members will be used to check for name clashes among
all members during the type checking phase. This means that if a
MixinDeclaration
node has multiple in-edges i.e. multiple classes/mixins mixwith the same mixin, then we would have to fetch the indirect members of that
MixinDeclaration
multiple times. We don’t need to perform this indirect memberfetching graph traversal multiple times because
collected_members_
acts as amemoized cache that stores the direct and indirect members of all classes/mixins
after they are encountered once during the graph traversal that happens during
type checking. We need not worry about loops forming in the graph at the type
checking phase because the name resolution phase disallows mixing mixins that
haven’t been declared yet.
Mixin features implemented by this PR
Mixin features that are planned for the future
as well as the Self of an interface. This means the body of a mixin method
will be able to access other mixin members.
for
clause of a mixin declaration which will add constraintsto another class/mixin trying to mix that mixin. This means that the body of a
mixin method can access interface members that the classes mixing this mixin
implements.
changing the names of the members of mixin declarations
Timeline
- Mixed member access through class instance
- Imports (constraints on entities mixing with a mixin)
- private exports
declarations)
- impls in mixin declarations
- Fields and constructors