blob: 4eb53aa2b82a78e01c323d55b8325a8571e855ac [file] [log] [blame]
# 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))