From 33de1e8543aa507550b6e213ebe7eacf1282bad4 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Wed, 7 Feb 2024 10:48:40 +0100 Subject: [PATCH] feat: substitute type parameters when computing type of inherited members (#864) Closes #863 ### Summary of Changes We now also substitute type parameters when computing the type for inherited members in a member access. Example: ``` class C { attr member: T } class D sub C segment mySegment(p: D) { val a = p.member; } ``` `a` now get the correct type `Int` (previously it was `T`). --- .../language/typing/safe-ds-type-computer.ts | 20 ++++++---- .../main.sdstest | 38 ++++++++++++++++++- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts b/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts index 0c913269b..175dcc3ce 100644 --- a/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts +++ b/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts @@ -507,16 +507,22 @@ export class SafeDsTypeComputer { } const receiverType = this.computeType(node.receiver); - const unsubstitutedResult = memberType.updateNullability( - (receiverType.isNullable && node.isNullSafe) || memberType.isNullable, - ); + let result: Type = memberType; - // Substitute type parameters + // Substitute type parameters (must also work for inherited members) if (receiverType instanceof ClassType) { - return unsubstitutedResult.substituteTypeParameters(receiverType.substitutions); - } else { - return unsubstitutedResult; + const classContainingMember = getContainerOfType(node.member?.target.ref, isSdsClass); + const typeContainingMember = stream([receiverType], this.streamSupertypes(receiverType)).find( + (it) => it.declaration === classContainingMember, + ); + + if (typeContainingMember) { + result = result.substituteTypeParameters(typeContainingMember.substitutions); + } } + + // Update nullability + return result.updateNullability((receiverType.isNullable && node.isNullSafe) || memberType.isNullable); } private computeTypeOfArithmeticPrefixOperation(node: SdsPrefixOperation): Type { diff --git a/packages/safe-ds-lang/tests/resources/typing/expressions/member accesses/on class with type parameters/main.sdstest b/packages/safe-ds-lang/tests/resources/typing/expressions/member accesses/on class with type parameters/main.sdstest index f4e36a6e5..f96eeb0e2 100644 --- a/packages/safe-ds-lang/tests/resources/typing/expressions/member accesses/on class with type parameters/main.sdstest +++ b/packages/safe-ds-lang/tests/resources/typing/expressions/member accesses/on class with type parameters/main.sdstest @@ -6,9 +6,13 @@ class C { @Pure fun method() -> r: T } +class D sub C + @Pure fun nullableC() -> result: C? +@Pure fun nullableD() -> result: D? -segment mySegment(p: C) { +// Accessing own members +segment mySegment1(p: C) { // $TEST$ serialization Int »p.nonNullableMember«; // $TEST$ serialization Int? @@ -38,3 +42,35 @@ segment mySegment(p: C) { // $TEST$ serialization union<() -> (r: Int), literal> »nullableC()?.method«; } + +// Accessing inherited members +segment mySegment2(p: D) { + // $TEST$ serialization Int + »p.nonNullableMember«; + // $TEST$ serialization Int? + »p.nullableMember«; + // $TEST$ serialization () -> (r: Int) + »p.method«; + + // $TEST$ serialization Int + »p?.nonNullableMember«; + // $TEST$ serialization Int? + »p?.nullableMember«; + // $TEST$ serialization () -> (r: Int) + »p?.method«; + + + // $TEST$ serialization Int + »nullableD().nonNullableMember«; + // $TEST$ serialization Int? + »nullableD().nullableMember«; + // $TEST$ serialization () -> (r: Int) + »nullableD().method«; + + // $TEST$ serialization Int? + »nullableD()?.nonNullableMember«; + // $TEST$ serialization Int? + »nullableD()?.nullableMember«; + // $TEST$ serialization union<() -> (r: Int), literal> + »nullableD()?.method«; +}