| # Copyright 2019 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| from typing import List, Optional, Iterator |
| from difl.ir import * |
| from difl.changes import * |
| |
| |
| def _describe_struct(struct: Optional[Declaration]) -> str: |
| '''Describe a FIDL struct declaration in a way that might make sense to a developer.''' |
| assert struct is not None |
| assert isinstance(struct, Struct) |
| |
| if struct.method is None: |
| return 'struct {}'.format(struct.name) |
| else: |
| if struct.request: |
| return 'method {} request'.format(struct.method.name) |
| else: |
| return 'method {} response'.format(struct.method.name) |
| |
| |
| def _describe_decl(decl: Optional[Declaration]) -> str: |
| assert decl is not None |
| if isinstance(decl, Struct): |
| return _describe_struct(decl) |
| elif isinstance(decl, StructMember): |
| return 'struct member {}.{}'.format(decl.struct.name, decl.name) |
| elif isinstance(decl, TableMember): |
| return 'table member {}.{}'.format(decl.table.name, decl.name) |
| elif isinstance(decl, Protocol): |
| return 'protocol {}'.format(decl.name) |
| elif isinstance(decl, Method): |
| return 'method {}'.format(decl.name) |
| elif isinstance(decl, Table): |
| return 'table {}'.format(decl.name) |
| else: |
| raise Exception("Don't know how to describe {} {!r}".format( |
| type(decl).__name__, decl)) |
| |
| |
| def _describe_type(type: Type) -> str: |
| if isinstance(type, PrimitiveType): |
| return type.subtype |
| |
| nullable = '' |
| if isinstance(type, NullableType) and type.is_nullable: |
| nullable = '?' |
| |
| if isinstance(type, IdentifierType): |
| return type.identifier + nullable |
| |
| if isinstance(type, RequestType): |
| return 'request<{}>{}'.format(type.protocol, nullable) |
| |
| if isinstance(type, HandleType): |
| if type.handle_type == 'handle': |
| return 'handle' + nullable |
| else: |
| return 'handle<{}>{}'.format(type.handle_type, nullable) |
| |
| if isinstance(type, IdentifierType): |
| return type['subtype'] + nullable |
| |
| element_count = '' |
| if 'maybe_element_count' in type: |
| element_count = ':{}'.format(type['maybe_element_count']) |
| |
| if isinstance(type, StringType): |
| return 'string' + element_count + nullable |
| |
| if isinstance(type, VectorType): |
| return 'vector<{}>{}{}'.format( |
| _describe_type(type.element_type), element_count, nullable) |
| |
| if isinstance(type, ArrayType): |
| return 'array<{}>{}'.format( |
| _describe_type(type.element_type), element_count) |
| |
| raise Exception("Don't know how to describe type {!r}".format(type)) |
| |
| |
| def _describe_decl_type(decl: Optional[Declaration]) -> str: |
| assert decl is not None |
| assert isinstance(decl, (StructMember, TableMember)) |
| return _describe_type(decl.type) |
| |
| |
| abi_nonchanges = (StructMemberRenamed, DeclAdded, TableMemberAdded, |
| TableMemberRenamed, TableMemberReserved, |
| TableMemberUnreserved) |
| |
| |
| def abi_changes(changes: List[Change]) -> Iterator[ClassifiedChange]: |
| for change in changes: |
| ### General Changes |
| if isinstance(change, abi_nonchanges): |
| # these changes never break ABI at all |
| continue |
| |
| if isinstance(change, DeclRemoved): |
| decl = change.before |
| assert decl is not None |
| if isinstance(decl, (Struct, Protocol, Method, Table, XUnion)): |
| yield ClassifiedChange( |
| change, False, |
| '{} removed, make sure there are no remaining users'. |
| format(_describe_decl(decl))) |
| else: |
| raise Exception( |
| 'DeclRemoved for unexpected decl {!r}'.format(decl)) |
| |
| ### Struct Changes |
| elif isinstance(change, StructSizeChanged): |
| yield ClassifiedChange(change, True, '{} changed size'.format( |
| _describe_struct(change.after))) |
| elif isinstance(change, StructMemberRemoved): |
| yield ClassifiedChange(change, True, '{} removed'.format( |
| _describe_decl(change.before))) |
| elif isinstance(change, StructMemberAdded): |
| yield ClassifiedChange(change, True, '{} added'.format( |
| _describe_decl(change.after))) |
| elif isinstance(change, StructMemberSizeChanged): |
| yield ClassifiedChange(change, True, '{} changed size'.format( |
| _describe_decl(change.after))) |
| elif isinstance(change, StructMemberMoved): |
| yield ClassifiedChange(change, True, '{} moved'.format( |
| _describe_decl(change.after))) |
| elif isinstance(change, StructMemberTypeChanged): |
| yield ClassifiedChange(change, not change.compatible, |
| '{} changed type from {} to {}'.format( |
| _describe_decl(change.after), |
| _describe_decl_type(change.before), |
| _describe_decl_type(change.after))) |
| elif isinstance(change, StructMemberSplit): |
| yield ClassifiedChange( |
| change, False, |
| '{} split into {} which may or may not be fine'.format( |
| _describe_decl(change.before), ', '.join( |
| m.name for m in change.afters))) |
| elif isinstance(change, StructMemberJoined): |
| after_field = change.after |
| assert after_field is not None |
| assert isinstance(after_field, StructMember) |
| yield ClassifiedChange( |
| change, False, |
| '{} members {} joined into {} which may or may not be fine'. |
| format( |
| _describe_decl(change.before), ', '.join( |
| m.name for m in change.befores), after_field.name)) |
| |
| ### Table Changes |
| elif isinstance(change, TableMemberRemoved): |
| yield ClassifiedChange( |
| change, False, |
| "{} removed, it should have been marked as reserved".format( |
| _describe_decl(change.before))) |
| elif isinstance(change, TableMemberTypeChanged): |
| yield ClassifiedChange(change, not change.compatible, |
| '{} changed type from {} to {}'.format( |
| _describe_decl(change.after), |
| _describe_decl_type(change.before), |
| _describe_decl_type(change.after))) |
| |
| ### Protocol Changes |
| elif isinstance(change, MethodOrdinalChanged): |
| yield ClassifiedChange(change, True, '{} ordinal changed'.format( |
| _describe_decl(change.before))) |
| elif isinstance(change, MethodBecameEvent): |
| yield ClassifiedChange(change, True, '{} became event'.format( |
| _describe_decl(change.before))) |
| elif isinstance(change, EventBecameMethod): |
| yield ClassifiedChange(change, True, '{} became method'.format( |
| _describe_decl(change.before))) |
| elif isinstance(change, MethodGainedResponse): |
| yield ClassifiedChange(change, True, '{} gained response'.format( |
| _describe_decl(change.before))) |
| elif isinstance(change, MethodLostResponse): |
| yield ClassifiedChange(change, True, '{} lost response'.format( |
| _describe_decl(change.before))) |
| |
| else: |
| raise Exception( |
| "Don't know if change is ABI breaking {!r}".format(change)) |