AaC Inheritance

Version 0.4.0 changed how inheritance works in AaC. AaC now provides a more “standard” inheritance construct within the schema data declaration using the extends field. By “standard”, we mean you can extend your schema in the same way you would extend a Python class since AaC uses Python under the hood.

In short, you can extend schemas using an object-oriented mindset. That said, this only applies to schema. While it is technically possible to extend an Enum in Python, AaC does not support enum extension. This may be revisited in the future, but initial attempts to support Enum extension in the underlying Python code generation did not work. And of course, extending primitives is not a thing, but you can define new primitives.

Modeling AaC Inheritance

Let’s take a look at how AaC supports Schema inheritance.

Example AaC Schema extension:

schema:
    name: Shape
    package: aac.shapes
    modifiers:
        - abstract
    fields:
        - name: area
          type: number
        - name: perimeter
          type: number

We’ve defined a generic Shape schema above. All shapes have a perimeter and area value, so the generic Shape schema also has a perimeter and area field. Shapes which inherit from Shape will also inherit these fields. Below, we will define other schemas which inherit from Shape:

schema:
    name: Triangle
    package: aac.shapes
    extends:
        - name: Shape
          package: aac.shapes
    fields:
        - name: side_one_length
          type: number
        - name: side_two_length
          type: number
        - name: side_three_length
          type: number
        - name: height
          type: number
---
schema:
    name: Circle
    package: aac.shapes
    extends:
        - name: Shape
          package: aac.shapes
    fields:
        - name: radius
          type: number
---
schema:
    name: Rectangle
    package: aac.shapes
    extends:
        - name: Shape
          package: aac.shapes
    fields:
        - name: length
          type: number
        - name: height
          type: number

Each of the above schemas have their own properties that help distinguish them as their specific shape. But they all require a perimeter and area field as a valid shape, and so inherit from Shape.

Inheritance Modifiers

Just as in most programming languages, the AaC language provides some basic modifiers to control what you can and can’t do with inherited schema definitions.

  • abstract - This attribute allows declaring a schema as an abstract type. This means it cannot be “instantiated” directly in a definition. In AaC, this means the schema is either a root type or field in another Schema.

  • final - This allows declaring a schema as a final type, meaning it cannot be extended by another Schema. In AaC this means the final schema cannot appear in another schema’s extends field.

To use these inheritance modifiers you simply add them to the modifiers field of the schema. Let’s take another look at the above example to see how it works:

schema:
    name: Shape
    package: aac.shapes
    modifiers:
        - abstract
    fields:
        - name: area
          type: number
        - name: perimeter
          type: number

As you can see, the Shape schema has an abstract modifier. This means that Shape can no longer be instantiated on its own. To make a shape, you must create or use a schema which inherits from Shape.

schema:
    name: Square
    package: aac.shapes
    extends:
        - name: Rectangle
          package: aac.shapes
    modifiers:
        - final
    fields:
        - name: length
          type: number
    requirements:
        - "SQR-1"

The above Square schema inherits from Rectangle as well via inheriting from Rectangle, but it has a final modifier. This means that no other schema can inherit from Square.

Using Inheritance

Now that we can define inheritance, how can we use it. The intended use case is to allow polymorphism in your field declaration. Let’s look at another example to explain.

schema:
    name: Pattern
    package: aac.shapes
    fields:
        - name: name
          type: string
        - name: shapes
          type: typeref(aac.shapes.Shape)[]

The above pattern schema has a shapes field which takes in a number of shapes to create a pattern with. Since we cannot instantiate the Shape schema on its own, we must use a schema which extends Shape. Since the field takes in any Shape, however, we can use any schema which extends Shape.

Required Fields

If a definition is extending another definition with required fields, then the is_required property of the fields will not carry over.

schema:
  name: AacType
  package: aac.lang
  modifiers:
    - abstract
  description: |
    The base type for any data item defined in AaC.
  fields:
    - name: name
      type: string
      description: |
        The name of the type.
      is_required: true
    - name: package
      type: string
      description: |
        The 'dot notation' package name for the type.  All type names must be unique within an assigned type.
        The package will also define the directory structure produced by gen-plugin.
      is_required: true
    - name: description
      type: string
      description: |
        A brief description of the type.
      is_required: true
schema:
  name: Schema
  extends:
    - package: aac.lang
      name: AacType
  package: aac.lang
  root: schema
  description: |
    A definition that defines the schema/structure of data.
  fields:
    - name: extends
      type: SchemaExtension[]
      description: |
        A list of Schema definition names that this definition inherits from.
    - name: modifiers
      type: dataref(modifier.name)[]
      description:
        A means of further defining the schema and how it can be used within the model.
    - name: root
      type: string
      description: |
        The root key to use when declaring an instance of the type in yaml/aac files.
    - name: fields
      type: Field[]
      description: |
        A list of fields that make up the definition.
      is_required: true
    - name: requirements
      type: dataref(req.id)[]
      description: |
        A list of requirements associated with this schema.
    - name: constraints
      type: SchemaConstraintAssignment[]

In the above example, Schema is an extension of AaCType. So while a Schema definition can use the name, description, and package fields from AaCType, the is_required field does not carry over. If you want an extension’s field to be required, you will have to redefine the field.