@@ -1371,6 +1371,7 @@ def connect(m, *args, **kwargs):
13711371 * For a given path, if any of the interface objects has an input port member corresponding
13721372 to a constant value, then the rest of the interface objects must have output port members
13731373 corresponding to the same constant value.
1374+ * When connecting multiple interface objects, at least one connection must be made.
13741375
13751376 For example, if :py:`obj1` is being connected to :py:`obj2` and :py:`obj3`, and :py:`obj1.a.b`
13761377 is an output, then :py:`obj2.a.b` and :py:`obj2.a.b` must exist and be inputs. If :py:`obj2.c`
@@ -1420,10 +1421,15 @@ def connect(m, *args, **kwargs):
14201421 reasons_as_string )
14211422 signatures [handle ] = obj .signature
14221423
1423- # Collate signatures and build connections.
1424+ # Connecting zero or one signatures is OK.
1425+ if len (signatures ) <= 1 :
1426+ return
1427+
1428+ # Collate signatures, build connections, track whether we see any input or output.
14241429 flattens = {handle : iter (sorted (signature .members .flatten ()))
14251430 for handle , signature in signatures .items ()}
14261431 connections = []
1432+ any_in , any_out = False , False
14271433 # Each iteration of the outer loop is intended to connect several (usually a pair) members
14281434 # to each other, e.g. an out member `[0].a` to an in member `[1].a`. However, because we
14291435 # do not just check signatures for equality (in order to improve diagnostics), it is possible
@@ -1437,6 +1443,7 @@ def connect(m, *args, **kwargs):
14371443 # implied in the flow of each port member, so the signature members are only classified
14381444 # here to ensure they are not connected to port members.
14391445 is_first = True
1446+ first_path = None
14401447 sig_kind , out_kind , in_kind = [], [], []
14411448 for handle , flattened_members in flattens .items ():
14421449 path_for_handle , member = next (flattened_members , (None , None ))
@@ -1499,6 +1506,8 @@ def connect(m, *args, **kwargs):
14991506 # There are no port members at this point; we're done with this path.
15001507 continue
15011508 # There are only port members after this point.
1509+ any_in = any_in or bool (in_kind )
1510+ any_out = any_out or bool (out_kind )
15021511 is_first = True
15031512 for (path , member ) in in_kind + out_kind :
15041513 member_shape = member .shape
@@ -1574,6 +1583,14 @@ def connect_dimensions(dimensions, *, out_path, in_path):
15741583 out_path = (* out_path , index ), in_path = (* in_path , index ))
15751584 assert out_member .dimensions == in_member .dimensions
15761585 connect_dimensions (out_member .dimensions , out_path = out_path , in_path = in_path )
1586+
1587+ # If no connections were made, and there were inputs but no outputs in the
1588+ # signatures, issue a diagnostic as this is most likely in error.
1589+ if len (connections ) == 0 and any_in and not any_out :
1590+ raise ConnectionError (f"Only input to input connections have been made between several "
1591+ f"interfaces; should one of them have been flipped?" )
1592+
1593+
15771594 # Now that we know all of the connections are legal, add them to the module. This is done
15781595 # instead of returning them because adding them to a non-comb domain would subtly violate
15791596 # assumptions that `connect()` is intended to provide.
0 commit comments