Index: mojom/mojom_tool/mojom/user_defined_types.go |
diff --git a/mojom/mojom_tool/mojom/user_defined_types.go b/mojom/mojom_tool/mojom/user_defined_types.go |
index 0234bfeb76366d3fa472e433dbf79ddfa34ecf3f..ac8c05cb6df81a5cf901b34502ee980c9a05437a 100644 |
--- a/mojom/mojom_tool/mojom/user_defined_types.go |
+++ b/mojom/mojom_tool/mojom/user_defined_types.go |
@@ -5,6 +5,7 @@ |
package mojom |
import ( |
+ "bytes" |
"errors" |
"fmt" |
"math" |
@@ -83,6 +84,30 @@ type UserDefinedType interface { |
// See computed_data.go. |
ComputeFinalData() error |
+ // CheckWellFounded() is invoked on each user-defined type in a MojomDescriptor |
+ // after the resolution and type validation phases have completed successfully. |
+ // The method performs an analysis of the type graph below a given type in order |
+ // to detect ill-founded types. An ill-founded type is one for which it is impossible |
+ // to create an instance that would be legally serializable using Mojo |
+ // serialization. This method returns a non-nil error just in case an |
+ // ill-founded type is detected. The contained error message is appropriate |
+ // for display to an end-user. |
+ // |
+ // An example of an ill-founded type is a struct |Foo| with a field whose type |
+ // is a non-nullable Foo. Thus ill-foundedness may be due to a cycle in the |
+ // type graph. But not all cycles cause ill-foundedness. Firstly nullable |
+ // fields do not lead to ill-foundedness as the potential cycle can be broken |
+ // by setting the field to null. Also the situation with unions is more complicated: |
+ // Type graphs involving unions are only ill-founded if every possible way of |
+ // chosing a value for all of the unions still leads to an unbreakable cycle. |
+ // |
+ // We model this using the notion of a well-founded two-sorted graph. See |
+ // utils/well-founded_graphs.go. Using the terminology from that file, we |
+ // model structs as circle nodes and unions as square nodes. In this context |
+ // the type graph only includes edges for non-nullable fields and arrays of |
+ // fixed positive length. |
+ CheckWellFounded() error |
+ |
// SerializationSize() returns the number of bytes necessary to serialize an |
// instance of this type in Mojo serialization. |
SerializationSize() uint32 |
@@ -92,12 +117,19 @@ type UserDefinedType interface { |
SerializationAlignment() uint32 |
} |
+///////////////////////////////////////////////////////////// |
+// type UserDefinedTypeBase |
+///////////////////////////////////////////////////////////// |
+ |
// This struct is embedded in each of MojomStruct, MojomInterface |
// MojomEnum and MojomUnion |
type UserDefinedTypeBase struct { |
DeclarationData |
thisType UserDefinedType |
typeKey string |
+ |
+ // Cache the fact that |SetKnownWellFounded| has been invoked. |
+ knownWellFounded bool |
} |
// This method is invoked from the constructors for the containing types: |
@@ -154,6 +186,101 @@ func (b UserDefinedTypeBase) Scope() *Scope { |
return b.scope |
} |
+func (b *UserDefinedTypeBase) CheckWellFounded() error { |
+ cycleDescription := utils.CheckWellFounded(b) |
+ if cycleDescription != nil { |
+ firstIllFoundedType := nodeToUserDefinedType(cycleDescription.First) |
+ var buffer bytes.Buffer |
+ fmt.Fprintf(&buffer, "The type %s is unserializable: Every instance of this type would include a cycle.", |
+ firstIllFoundedType.FullyQualifiedName()) |
+ fmt.Fprintf(&buffer, "\nExample cycle: %s", firstIllFoundedType.SimpleName()) |
+ for _, edge := range cycleDescription.Path { |
+ fmt.Fprintf(&buffer, ".%s -> %s", edge.Label.(lexer.Token).Text, nodeToUserDefinedType(edge.Target).SimpleName()) |
+ } |
+ fmt.Fprintf(&buffer, "\n") |
+ fmt.Fprintf(&buffer, "One way to break this cycle is to make the field %q nullable.", cycleDescription.Path[0].Label.(lexer.Token).Text) |
+ return fmt.Errorf(UserErrorMessage( |
+ firstIllFoundedType.Scope().file, cycleDescription.Path[0].Label.(lexer.Token), buffer.String())) |
+ } |
+ return nil |
+} |
+ |
+// *UserDefinedTypeBase implements utils.Node for the sake of |CheckWellFounded|. |
+func (b *UserDefinedTypeBase) KnownWellFounded() bool { |
+ return b.knownWellFounded |
+} |
+ |
+// *UserDefinedTypeBase implements utils.Node for the sake of |CheckWellFounded|. |
+func (b *UserDefinedTypeBase) SetKnownWellFounded() { |
+ b.knownWellFounded = true |
+} |
+ |
+// *UserDefinedTypeBase implements utils.Node for the sake of |CheckWellFounded|. |
+func (b *UserDefinedTypeBase) Name() string { |
+ return b.FullyQualifiedName() |
+} |
+ |
+// *UserDefinedTypeBase implements utils.Node for the sake of |CheckWellFounded|. |
+func (b *UserDefinedTypeBase) IsSquare() bool { |
+ // A node in the type graph is square if and only if the type is a union. |
+ return b.thisType.Kind() == UserDefinedTypeKindUnion |
+} |
+ |
+// *UserDefinedTypeBase implements utils.Node for the sake of |CheckWellFounded|. |
+func (b *UserDefinedTypeBase) OutEdges() []utils.OutEdge { |
+ // The only types we need to model as nodes are structs and unions. |
+ // We model some of their fields as children: fields whose type is a non-nullable |
+ // pointer or a fixed-length array to another struct or union. |
+ outEdges := make([]utils.OutEdge, 0) |
+ switch udt := b.thisType.(type) { |
+ case *MojomEnum, *MojomInterface: |
+ return nil |
+ case *MojomStruct: |
+ for _, field := range udt.FieldsInLexicalOrder { |
+ childType := field.FieldType.NonAvoidableUserType() |
+ if childType != nil { |
+ outEdges = append(outEdges, utils.OutEdge{field.nameToken, getTypeBase(childType)}) |
+ } |
+ } |
+ case *MojomUnion: |
+ for _, field := range udt.FieldsInLexicalOrder { |
+ childType := field.FieldType.NonAvoidableUserType() |
+ if childType != nil { |
+ outEdges = append(outEdges, utils.OutEdge{field.nameToken, getTypeBase(childType)}) |
+ } |
+ } |
+ default: |
+ panic(fmt.Sprintf("unexpected kind %T", udt)) |
+ } |
+ return outEdges |
+} |
+ |
+// getTypeBase returns the *UserDefinedTypeBase contained in the given UserDefinedType |
+func getTypeBase(udt UserDefinedType) *UserDefinedTypeBase { |
+ switch udt := udt.(type) { |
+ case *MojomStruct: |
+ return &udt.UserDefinedTypeBase |
+ case *MojomUnion: |
+ return &udt.UserDefinedTypeBase |
+ case *MojomEnum: |
+ return &udt.UserDefinedTypeBase |
+ case *MojomInterface: |
+ return &udt.UserDefinedTypeBase |
+ default: |
+ panic(fmt.Sprintf("Unrecognized user defined type %T", udt)) |
+ } |
+} |
+ |
+// nodeToUserDefinedType assumes that the type of |node| is *UserDefinedTypeBase |
+// and returns the associated UserDefinedType |
+func nodeToUserDefinedType(node utils.Node) UserDefinedType { |
+ return node.(*UserDefinedTypeBase).thisType |
+} |
+ |
+///////////////////////////////////////////////////////////// |
+// type DeclaredObjectsContainerBase |
+///////////////////////////////////////////////////////////// |
+ |
// DeclaredObjectsContainerBase holds a list of DeclaredObjects in order of |
// occurrence in the source. |
// It includes all declared objects (for example methods and fields) not just |
@@ -181,6 +308,10 @@ type DeclaredObjectsContainer interface { |
GetDeclaredObjects() []DeclaredObject |
} |
+///////////////////////////////////////////////////////////// |
+// type NestedDeclarations |
+///////////////////////////////////////////////////////////// |
+ |
// Some user-defined types, namely interfaces and structs, may act as |
// namespaced scopes for declarations of constants and enums. |
type NestedDeclarations struct { |