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«; +}