@@ -382,9 +382,34 @@ const checkObjectType = (obj: ObjectType): ObjectType => {
382
382
field . type = type ;
383
383
} ) ;
384
384
385
+ if ( obj . parentObjExpr ) {
386
+ obj . parentObjExpr = checkTypes ( obj . parentObjExpr ) ;
387
+ const parentType = resolveExprType ( obj . parentObjExpr ) ;
388
+ assertValidExtension ( obj , parentType ) ;
389
+ obj . parentObj = parentType ;
390
+ }
391
+
385
392
return obj ;
386
393
} ;
387
394
395
+ function assertValidExtension (
396
+ child : ObjectType ,
397
+ parent ?: Type
398
+ ) : asserts parent is ObjectType {
399
+ if ( ! parent || ! parent ?. isObjectType ( ) ) {
400
+ throw new Error ( `Cannot resolve parent for obj ${ child . name } ` ) ;
401
+ }
402
+
403
+ const validExtension = parent . fields . every ( ( field ) => {
404
+ const match = child . fields . find ( ( f ) => f . name === field . name ) ;
405
+ return match && typesAreEquivalent ( field . type , match . type ) ;
406
+ } ) ;
407
+
408
+ if ( ! validExtension ) {
409
+ throw new Error ( `${ child . name } does not properly extend ${ parent . name } ` ) ;
410
+ }
411
+ }
412
+
388
413
const checkTypeAlias = ( alias : TypeAlias ) : TypeAlias => {
389
414
alias . typeExpr = checkTypes ( alias . typeExpr ) ;
390
415
alias . type = resolveExprType ( alias . typeExpr ) ;
@@ -452,9 +477,7 @@ const getIdentifierType = (id: Identifier): Type | undefined => {
452
477
} ;
453
478
454
479
const resolveCallFn = ( call : Call ) : Fn | undefined => {
455
- const candidates = call . resolveFns ( call . fnName ) ;
456
- if ( ! candidates ) return undefined ;
457
- return candidates . find ( ( candidate ) => {
480
+ const candidates = call . resolveFns ( call . fnName ) . filter ( ( candidate ) => {
458
481
const params = candidate . parameters ;
459
482
return params . every ( ( p , index ) => {
460
483
const arg = call . argAt ( index ) ;
@@ -468,6 +491,54 @@ const resolveCallFn = (call: Call): Fn | undefined => {
468
491
return typesAreEquivalent ( p . type ! , argType ) && labelsMatch ;
469
492
} ) ;
470
493
} ) ;
494
+
495
+ if ( ! candidates ) return undefined ;
496
+ if ( candidates . length === 1 ) return candidates [ 0 ] ;
497
+ return findBestFnMatch ( candidates , call ) ;
498
+ } ;
499
+
500
+ const findBestFnMatch = ( candidates : Fn [ ] , call : Call ) : Fn => {
501
+ let winner : Fn | undefined = undefined ;
502
+ let tied = false ;
503
+ let lowestScore : number | undefined ;
504
+ for ( const candidate of candidates ) {
505
+ const score = candidate . parameters . reduce ( ( score , param , index ) => {
506
+ if ( ! param . type ?. isObjectType ( ) ) {
507
+ return score ;
508
+ }
509
+
510
+ const argType = resolveExprType ( call . argAt ( index ) ) ;
511
+ if ( ! argType || ! argType . isObjectType ( ) ) {
512
+ throw new Error ( `Could not determine type. I'm helpful >.<` ) ;
513
+ }
514
+
515
+ return ( score += argType . extensionDistance ( param . type ) ) ;
516
+ } , 0 ) ;
517
+
518
+ if ( lowestScore === undefined ) {
519
+ lowestScore = score ;
520
+ winner = candidate ;
521
+ }
522
+
523
+ if ( score > lowestScore ) {
524
+ continue ;
525
+ }
526
+
527
+ if ( score < lowestScore ) {
528
+ lowestScore = score ;
529
+ winner = candidate ;
530
+ tied = false ;
531
+ continue ;
532
+ }
533
+
534
+ tied = true ;
535
+ }
536
+
537
+ if ( ! winner || tied ) {
538
+ throw new Error ( `Ambiguous call ${ JSON . stringify ( call , null , 2 ) } ` ) ;
539
+ }
540
+
541
+ return winner ;
471
542
} ;
472
543
473
544
const getExprLabel = ( expr ?: Expr ) : string | undefined => {
@@ -486,10 +557,13 @@ const typesAreEquivalent = (a?: Type, b?: Type): boolean => {
486
557
}
487
558
488
559
if ( a . isObjectType ( ) && b . isObjectType ( ) ) {
489
- return a . fields . every ( ( field ) => {
490
- const match = b . fields . find ( ( f ) => f . name === field . name ) ;
491
- return match && typesAreEquivalent ( field . type , match . type ) ;
492
- } ) ;
560
+ return (
561
+ a . extends ( b ) &&
562
+ a . fields . every ( ( field ) => {
563
+ const match = b . fields . find ( ( f ) => f . name === field . name ) ;
564
+ return match && typesAreEquivalent ( field . type , match . type ) ;
565
+ } )
566
+ ) ;
493
567
}
494
568
495
569
return false ;
0 commit comments