From 0e657cf81e20965640a840c09562224e5eee3802 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 26 Feb 2024 11:20:48 +0100 Subject: [PATCH] feat: substitute type parameters when checking overridden members (#922) Closes #917 ### Summary of Changes When checking whether overriding is legal and needed, we now use strict type checking and correctly substitute type parameters. --- .../language/typing/safe-ds-type-checker.ts | 5 +- .../language/typing/safe-ds-type-computer.ts | 82 ++++++++++---- .../src/language/validation/inheritance.ts | 103 ++++++++++++++---- .../operations/arithmetic/main.sdstest | 12 ++ .../type parameters.sdstest | 40 +++++-- .../type parameters.sdstest | 35 ++++-- .../indexed access receiver/main.sdstest | 17 ++- 7 files changed, 220 insertions(+), 74 deletions(-) diff --git a/packages/safe-ds-lang/src/language/typing/safe-ds-type-checker.ts b/packages/safe-ds-lang/src/language/typing/safe-ds-type-checker.ts index 04f4515e9..b08c80fbc 100644 --- a/packages/safe-ds-lang/src/language/typing/safe-ds-type-checker.ts +++ b/packages/safe-ds-lang/src/language/typing/safe-ds-type-checker.ts @@ -21,20 +21,17 @@ import { SafeDsClassHierarchy } from './safe-ds-class-hierarchy.js'; import { SafeDsCoreTypes } from './safe-ds-core-types.js'; import type { SafeDsTypeComputer } from './safe-ds-type-computer.js'; import { isEmpty } from '../../helpers/collections.js'; -import { SafeDsTypeFactory } from './safe-ds-type-factory.js'; export class SafeDsTypeChecker { private readonly builtinClasses: SafeDsClasses; private readonly classHierarchy: SafeDsClassHierarchy; private readonly coreTypes: SafeDsCoreTypes; - private readonly factory: SafeDsTypeFactory; private readonly typeComputer: () => SafeDsTypeComputer; constructor(services: SafeDsServices) { this.builtinClasses = services.builtins.Classes; this.classHierarchy = services.types.ClassHierarchy; this.coreTypes = services.types.CoreTypes; - this.factory = services.types.TypeFactory; this.typeComputer = () => services.types.TypeComputer; } @@ -398,6 +395,7 @@ export class SafeDsTypeChecker { !type.equals(this.coreTypes.NothingOrNull) && this.isSubtypeOf(type, listOrNull, { ignoreTypeParameters: true, + strictTypeParameterTypeCheck: true, }) ); } @@ -413,6 +411,7 @@ export class SafeDsTypeChecker { !type.equals(this.coreTypes.NothingOrNull) && this.isSubtypeOf(type, mapOrNull, { ignoreTypeParameters: true, + strictTypeParameterTypeCheck: true, }) ); } 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 ab2ccdca0..be05c5475 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 @@ -55,7 +55,6 @@ import { SdsAssignee, type SdsBlockLambda, SdsCall, - SdsCallable, SdsCallableType, SdsClass, SdsDeclaration, @@ -458,11 +457,7 @@ export class SafeDsTypeComputer { // Substitute type parameters if (isSdsFunction(nonNullableReceiverType.callable)) { - const substitutions = this.computeTypeParameterSubstitutionsForCallable( - nonNullableReceiverType.callable, - node, - ); - + const substitutions = this.computeTypeParameterSubstitutionsForCall(node); result = result.substituteTypeParameters(substitutions); } } else if (nonNullableReceiverType instanceof StaticType) { @@ -473,7 +468,7 @@ export class SafeDsTypeComputer { // Substitute type parameters if (instanceType instanceof ClassType) { - const substitutions = this.computeTypeParameterSubstitutionsForCallable(instanceType.declaration, node); + const substitutions = this.computeTypeParameterSubstitutionsForCall(node); result = this.factory.createClassType( instanceType.declaration, @@ -533,8 +528,8 @@ export class SafeDsTypeComputer { const rightOperandType = this.computeType(node.rightOperand); if ( - this.typeChecker.isSubtypeOf(leftOperandType, this.coreTypes.Int) && - this.typeChecker.isSubtypeOf(rightOperandType, this.coreTypes.Int) + this.typeChecker.isSubtypeOf(leftOperandType, this.coreTypes.Int, { strictTypeParameterTypeCheck: true }) && + this.typeChecker.isSubtypeOf(rightOperandType, this.coreTypes.Int, { strictTypeParameterTypeCheck: true }) ) { return this.coreTypes.Int; } else { @@ -586,7 +581,7 @@ export class SafeDsTypeComputer { private computeTypeOfArithmeticPrefixOperation(node: SdsPrefixOperation): Type { const operandType = this.computeType(node.operand); - if (this.typeChecker.isSubtypeOf(operandType, this.coreTypes.Int)) { + if (this.typeChecker.isSubtypeOf(operandType, this.coreTypes.Int, { strictTypeParameterTypeCheck: true })) { return this.coreTypes.Int; } else { return this.coreTypes.Float; @@ -783,13 +778,13 @@ export class SafeDsTypeComputer { // ----------------------------------------------------------------------------------------------------------------- /** - * Computes substitutions for the type parameters of the given callable in the context of the given call. + * Computes substitutions for the type parameters of a callable in the context of a call. * - * @param callable The callable with the type parameters to compute substitutions for. * @param call The call to compute substitutions for. * @returns The computed substitutions for the type parameters of the callable. */ - computeTypeParameterSubstitutionsForCallable(callable: SdsCallable, call: SdsCall): TypeParameterSubstitutions { + computeTypeParameterSubstitutionsForCall(call: SdsCall): TypeParameterSubstitutions { + const callable = this.nodeMapper.callToCallable(call); const typeParameters = getTypeParameters(callable); if (isEmpty(typeParameters)) { return NO_SUBSTITUTIONS; @@ -804,7 +799,36 @@ export class SafeDsTypeComputer { return [this.computeType(parameter.type), this.computeType(argument?.value ?? parameter.defaultValue)]; }); - return this.computeTypeParameterSubstitutionsForParameters(typeParameters, parameterTypesToArgumentTypes); + return this.computeTypeParameterSubstitutionsForArguments(typeParameters, parameterTypesToArgumentTypes); + } + + /** + * Computes substitutions for the type parameters of a callable in the context of overriding another callable. + * + * @param ownMemberType The type of the overriding callable. + * @param overriddenMemberType The type of the overridden callable. + */ + computeSubstitutionsForOverriding(ownMemberType: Type, overriddenMemberType: Type): TypeParameterSubstitutions { + if (!(ownMemberType instanceof CallableType) || !(overriddenMemberType instanceof CallableType)) { + return NO_SUBSTITUTIONS; + } + + const ownTypeParameters = getTypeParameters(ownMemberType.callable); + if (isEmpty(ownTypeParameters)) { + return NO_SUBSTITUTIONS; + } + + const ownParameterTypes = ownMemberType.inputType.entries.map((it) => it.type); + const overriddenParameterTypes = overriddenMemberType.inputType.entries.map((it) => it.type); + + const minimumParameterCount = Math.min(ownParameterTypes.length, overriddenParameterTypes.length); + const ownTypesToOverriddenTypes: [Type, Type][] = []; + + for (let i = 0; i < minimumParameterCount; i++) { + ownTypesToOverriddenTypes.push([ownParameterTypes[i]!, overriddenParameterTypes[i]!]); + } + + return this.computeTypeParameterSubstitutionsForArguments(ownTypeParameters, ownTypesToOverriddenTypes); } /** @@ -815,7 +839,7 @@ export class SafeDsTypeComputer { * @param parameterTypesToArgumentTypes Pairs of parameter types and the corresponding argument types. * @returns The computed substitutions for the type parameters in the parameter types. */ - computeTypeParameterSubstitutionsForParameters( + private computeTypeParameterSubstitutionsForArguments( typeParameters: SdsTypeParameter[], parameterTypesToArgumentTypes: [Type, Type][], ): TypeParameterSubstitutions { @@ -850,7 +874,7 @@ export class SafeDsTypeComputer { stopAtTypeParameterType: true, }).substituteTypeParameters(state.substitutions); - if (!this.typeChecker.isSubtypeOf(newSubstitution, upperBound)) { + if (!this.typeChecker.isSubtypeOf(newSubstitution, upperBound, { strictTypeParameterTypeCheck: true })) { newSubstitution = upperBound; } @@ -1216,11 +1240,18 @@ export class SafeDsTypeComputer { } private isCommonSupertypeIgnoringTypeParameters(candidate: Type, otherTypes: Type[]): boolean { - return otherTypes.every((it) => this.typeChecker.isSupertypeOf(candidate, it, { ignoreTypeParameters: true })); + return otherTypes.every((it) => + this.typeChecker.isSupertypeOf(candidate, it, { + ignoreTypeParameters: true, + strictTypeParameterTypeCheck: true, + }), + ); } private isCommonSupertype(candidate: Type, otherTypes: Type[]): boolean { - return otherTypes.every((it) => this.typeChecker.isSupertypeOf(candidate, it)); + return otherTypes.every((it) => + this.typeChecker.isSupertypeOf(candidate, it, { strictTypeParameterTypeCheck: true }), + ); } // ----------------------------------------------------------------------------------------------------------------- @@ -1245,9 +1276,7 @@ export class SafeDsTypeComputer { const typesWithMatchingNullability = types.map((it) => it.withExplicitNullability(isNullable)); // One of the types is already a common subtype of all others after updating nullability - const commonSupertype = typesWithMatchingNullability.find((it) => - this.isCommonSubtypeWithStrictTypeParameterTypeCheck(it, types), - ); + const commonSupertype = typesWithMatchingNullability.find((it) => this.isCommonSubtype(it, types)); if (commonSupertype) { return commonSupertype; } @@ -1318,7 +1347,7 @@ export class SafeDsTypeComputer { // If the class has no type parameters, the candidate must match as is const typeParameters = getTypeParameters(candidate.declaration); if (isEmpty(typeParameters)) { - if (this.isCommonSubtypeWithStrictTypeParameterTypeCheck(candidate, others)) { + if (this.isCommonSubtype(candidate, others)) { /* c8 ignore next 2 */ return candidate; } else { @@ -1454,10 +1483,15 @@ export class SafeDsTypeComputer { } private isCommonSubtypeIgnoringTypeParameters(candidate: Type, otherTypes: Type[]): boolean { - return otherTypes.every((it) => this.typeChecker.isSubtypeOf(candidate, it, { ignoreTypeParameters: true })); + return otherTypes.every((it) => + this.typeChecker.isSubtypeOf(candidate, it, { + ignoreTypeParameters: true, + strictTypeParameterTypeCheck: true, + }), + ); } - private isCommonSubtypeWithStrictTypeParameterTypeCheck(candidate: Type, otherTypes: Type[]): boolean { + private isCommonSubtype(candidate: Type, otherTypes: Type[]): boolean { return otherTypes.every((it) => this.typeChecker.isSubtypeOf(candidate, it, { strictTypeParameterTypeCheck: true }), ); diff --git a/packages/safe-ds-lang/src/language/validation/inheritance.ts b/packages/safe-ds-lang/src/language/validation/inheritance.ts index 6f3abb8a8..34e335868 100644 --- a/packages/safe-ds-lang/src/language/validation/inheritance.ts +++ b/packages/safe-ds-lang/src/language/validation/inheritance.ts @@ -3,7 +3,8 @@ import { isEmpty, isEqualSet } from '../../helpers/collections.js'; import { isSdsClass, isSdsFunction, SdsClass, type SdsClassMember } from '../generated/ast.js'; import { getParentTypes, getQualifiedName } from '../helpers/nodeProperties.js'; import { SafeDsServices } from '../safe-ds-module.js'; -import { ClassType, UnknownType } from '../typing/model.js'; +import { ClassType, Type, UnknownType } from '../typing/model.js'; +import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js'; export const CODE_INHERITANCE_CYCLE = 'inheritance/cycle'; export const CODE_INHERITANCE_MULTIPLE_INHERITANCE = 'inheritance/multiple-inheritance'; @@ -25,29 +26,16 @@ export const classMemberMustMatchOverriddenMemberAndShouldBeNeeded = (services: return; } - // Compute basic types (might contain type parameters) - const ownMemberType = typeComputer.computeType(node); - let overriddenMemberType = typeComputer.computeType(overriddenMember); + // Compute types + const { ownMemberType, overriddenMemberType, substitutedOwnMemberType, substitutedOverriddenMemberType } = + computeMemberTypes(node, overriddenMember, typeComputer); - // Substitute type parameters on overriddenMemberType - const classContainingOwnMember = getContainerOfType(node, isSdsClass); - const typeContainingOwnMember = typeComputer.computeType(classContainingOwnMember); - - if (typeContainingOwnMember instanceof ClassType) { - const classContainingOverriddenMember = getContainerOfType(overriddenMember, isSdsClass); - const typeContainingOverriddenMember = typeComputer.computeMatchingSupertype( - typeContainingOwnMember, - classContainingOverriddenMember, - ); - - if (typeContainingOverriddenMember) { - overriddenMemberType = overriddenMemberType.substituteTypeParameters( - typeContainingOverriddenMember.substitutions, - ); - } - } - - if (!typeChecker.isSubtypeOf(ownMemberType, overriddenMemberType)) { + // Check whether the overriding is legal and needed + if ( + !typeChecker.isSubtypeOf(substitutedOwnMemberType, overriddenMemberType, { + strictTypeParameterTypeCheck: true, + }) + ) { accept( 'error', expandToStringWithNL` @@ -61,7 +49,11 @@ export const classMemberMustMatchOverriddenMemberAndShouldBeNeeded = (services: code: CODE_INHERITANCE_INCOMPATIBLE_TO_OVERRIDDEN_MEMBER, }, ); - } else if (typeChecker.isSubtypeOf(overriddenMemberType, ownMemberType)) { + } else if ( + typeChecker.isSubtypeOf(substitutedOverriddenMemberType, ownMemberType, { + strictTypeParameterTypeCheck: true, + }) + ) { // Prevents the info from showing when editing the builtin files if (isInSafedsLangAnyClass(services, node)) { return; @@ -94,6 +86,69 @@ export const classMemberMustMatchOverriddenMemberAndShouldBeNeeded = (services: }; }; +const computeMemberTypes = ( + ownMember: SdsClassMember, + overriddenMember: SdsClassMember, + typeComputer: SafeDsTypeComputer, +): ComputeMemberTypesResult => { + // Compute basic types (might contain type parameters) + const ownMemberType = typeComputer.computeType(ownMember); + let overriddenMemberType = typeComputer.computeType(overriddenMember); + + // Substitute type parameters of class containing the overridden member + const classContainingOwnMember = getContainerOfType(ownMember, isSdsClass); + const typeContainingOwnMember = typeComputer.computeType(classContainingOwnMember); + + if (typeContainingOwnMember instanceof ClassType) { + const classContainingOverriddenMember = getContainerOfType(overriddenMember, isSdsClass); + const typeContainingOverriddenMember = typeComputer.computeMatchingSupertype( + typeContainingOwnMember, + classContainingOverriddenMember, + ); + + if (typeContainingOverriddenMember) { + overriddenMemberType = overriddenMemberType.substituteTypeParameters( + typeContainingOverriddenMember.substitutions, + ); + } + } + + // Substitute type parameters of methods + const substitutedOwnMemberType = ownMemberType.substituteTypeParameters( + typeComputer.computeSubstitutionsForOverriding(ownMemberType, overriddenMemberType), + ); + const substitutedOverriddenMemberType = overriddenMemberType.substituteTypeParameters( + typeComputer.computeSubstitutionsForOverriding(overriddenMemberType, ownMemberType), + ); + + return { ownMemberType, overriddenMemberType, substitutedOwnMemberType, substitutedOverriddenMemberType }; +}; + +interface ComputeMemberTypesResult { + /** + * The type of the own member. Type parameters of the containing class or own member are not yet substituted. + */ + ownMemberType: Type; + + /** + * The type of the overridden member. Type parameters of the containing class are substituted, but not the type + * parameters of the overridden member. + */ + overriddenMemberType: Type; + + /** + * The type of the own member with all type parameters of the own member substituted. Substitutions are based on the + * types of the corresponding parameters of the overridden member. + */ + substitutedOwnMemberType: Type; + + /** + * The type of the overridden member with all type parameters of the overridden member substituted. Substitutions + * are based on the types of the corresponding parameters of the own member. + */ + substitutedOverriddenMemberType: Type; +} + const isInSafedsLangAnyClass = (services: SafeDsServices, node: SdsClassMember): boolean => { const containingClass = getContainerOfType(node, isSdsClass); return ( diff --git a/packages/safe-ds-lang/tests/resources/typing/expressions/operations/arithmetic/main.sdstest b/packages/safe-ds-lang/tests/resources/typing/expressions/operations/arithmetic/main.sdstest index 814bd7029..57046ac4a 100644 --- a/packages/safe-ds-lang/tests/resources/typing/expressions/operations/arithmetic/main.sdstest +++ b/packages/safe-ds-lang/tests/resources/typing/expressions/operations/arithmetic/main.sdstest @@ -140,3 +140,15 @@ pipeline mixedOperands { // $TEST$ serialization Float val divisionFloatFloat = »1.5 / anyFloat()«; } + +// Strict checking of type parameter types +class MyClass( + p1: T, + + // $TEST$ serialization Float + p2: Any? = »p1 + 1«, + // $TEST$ serialization Float + p3: Any? = »1 + p1«, + // $TEST$ serialization Float + p4: Any? = »-p1«, +) diff --git a/packages/safe-ds-lang/tests/resources/validation/inheritance/overriding method must match overridden method/type parameters.sdstest b/packages/safe-ds-lang/tests/resources/validation/inheritance/overriding method must match overridden method/type parameters.sdstest index a698956f7..04b5fcea0 100644 --- a/packages/safe-ds-lang/tests/resources/validation/inheritance/overriding method must match overridden method/type parameters.sdstest +++ b/packages/safe-ds-lang/tests/resources/validation/inheritance/overriding method must match overridden method/type parameters.sdstest @@ -1,20 +1,46 @@ -package tests.validation.inheritance.overridingMethodMustMatchOverriddenMethod +package tests.validation.inheritance.overridingMethodMustMatchOverriddenMethod.typeParameters -class MySuperClass2 { +class MySuperClass { attr myInstanceAttribute: T - @Pure fun myInstanceMethod(a: T = 0) -> r: T + @Pure fun myInstanceMethod1(a: T = 0) -> r: T + @Pure fun myInstanceMethod2(a: Int) -> r: Int + @Pure fun myInstanceMethod3(a: T) -> r: T } -class MyClass3 sub MySuperClass2 { +class MyClass1 sub MySuperClass { // $TEST$ no error r"Overriding member does not match the overridden member:.*" attr »myInstanceAttribute«: Int // $TEST$ no error r"Overriding member does not match the overridden member:.*" - @Pure fun »myInstanceMethod«(a: Any = 0) -> r: Int + @Pure fun »myInstanceMethod1«(a: Any = 0) -> r: Int } -class MyClass4 sub MySuperClass2 { +class MyClass2 sub MySuperClass { + // $TEST$ no error r"Overriding member does not match the overridden member:.*" + attr »myInstanceAttribute«: T + // $TEST$ no error r"Overriding member does not match the overridden member:.*" + @Pure fun »myInstanceMethod1«(a: T = 0) -> r: T +} + +class MyClass3 sub MySuperClass { // $TEST$ error r"Overriding member does not match the overridden member:.*" attr »myInstanceAttribute«: Any // $TEST$ error r"Overriding member does not match the overridden member:.*" - @Pure fun »myInstanceMethod«(a: Number = 0) -> r: Any + @Pure fun »myInstanceMethod1«(a: Number = 0) -> r: Any +} + +class MyClass4 sub MySuperClass { + // $TEST$ no error r"Overriding member does not match the overridden member:.*" + @Pure fun »myInstanceMethod2«(a: T) -> r: T + // $TEST$ no error r"Overriding member does not match the overridden member:.*" + @Pure fun »myInstanceMethod3«(a: T) -> r: T +} + +class MyClass5 sub MySuperClass { + // $TEST$ error r"Overriding member does not match the overridden member:.*" + @Pure fun »myInstanceMethod3«(a: Int) -> r: Int +} + +class MyClass6 sub MySuperClass { + // $TEST$ error r"Overriding member does not match the overridden member:.*" + attr »myInstanceAttribute«: T } diff --git a/packages/safe-ds-lang/tests/resources/validation/inheritance/overriding method should differ from overridden method/type parameters.sdstest b/packages/safe-ds-lang/tests/resources/validation/inheritance/overriding method should differ from overridden method/type parameters.sdstest index 0dddba5f3..d6e2216fc 100644 --- a/packages/safe-ds-lang/tests/resources/validation/inheritance/overriding method should differ from overridden method/type parameters.sdstest +++ b/packages/safe-ds-lang/tests/resources/validation/inheritance/overriding method should differ from overridden method/type parameters.sdstest @@ -1,20 +1,41 @@ -package tests.validation.inheritance.overridingMethodShouldDifferFromOverriddenMethod +package tests.validation.inheritance.overridingMethodShouldDifferFromOverriddenMethod.typeParameters -class MySuperClass3 { +class MySuperClass { attr myInstanceAttribute: T - @Pure fun myInstanceMethod(a: T = 0) -> r: T + @Pure fun myInstanceMethod1(a: T = 0) -> r: T + @Pure fun myInstanceMethod2(a: Int) -> r: Int + @Pure fun myInstanceMethod3(a: T) -> r: T } -class MyClass5 sub MySuperClass3 { +class MyClass2 sub MySuperClass { + // $TEST$ info "Overriding member is identical to overridden member and can be removed." + attr »myInstanceAttribute«: T + // $TEST$ info "Overriding member is identical to overridden member and can be removed." + @Pure fun »myInstanceMethod1«(a: T = 0) -> r: T +} + +class MyClass1 sub MySuperClass { // $TEST$ info "Overriding member is identical to overridden member and can be removed." attr »myInstanceAttribute«: Number // $TEST$ info "Overriding member is identical to overridden member and can be removed." - @Pure fun »myInstanceMethod«(a: Number = 0) -> r: Number + @Pure fun »myInstanceMethod1«(a: Number = 0) -> r: Number } -class MyClass6 sub MySuperClass3 { +class MyClass3 sub MySuperClass { // $TEST$ no info "Overriding member is identical to overridden member and can be removed." attr »myInstanceAttribute«: Int // $TEST$ no info "Overriding member is identical to overridden member and can be removed." - @Pure fun »myInstanceMethod«(a: Number = 0) -> r: Int + @Pure fun »myInstanceMethod1«(a: Number = 0) -> r: Int +} + +class MyClass4 sub MySuperClass { + // $TEST$ no info "Overriding member is identical to overridden member and can be removed." + @Pure fun »myInstanceMethod2«(a: T) -> r: T + // $TEST$ info "Overriding member is identical to overridden member and can be removed." + @Pure fun »myInstanceMethod3«(a: T) -> r: T +} + +class MyClass5 sub MySuperClass { + // $TEST$ no info "Overriding member is identical to overridden member and can be removed." + @Pure fun »myInstanceMethod3«(a: Int) -> r: Int } diff --git a/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access receiver/main.sdstest b/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access receiver/main.sdstest index f5dc8c4d0..7486419b5 100644 --- a/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access receiver/main.sdstest +++ b/packages/safe-ds-lang/tests/resources/validation/types/checking/indexed access receiver/main.sdstest @@ -13,31 +13,30 @@ segment mySegment( ) { // $TEST$ no error r"Expected type 'List' or 'Map' but got .*\." »[1]«[0]; - // $TEST$ no error r"Expected type 'List' or 'Map' but got .*\." »{0: 1}«[0]; - // $TEST$ error "Expected type 'List' or 'Map' but got 'literal<1>'." »1«[0]; - // $TEST$ no error r"Expected type 'List' or 'Map' but got .*\." »listOrNull«[0]; - // $TEST$ no error r"Expected type 'List' or 'Map' but got .*\." »mapOrNull«[""]; - // $TEST$ error "Expected type 'List' or 'Map' but got 'Int?'." »intOrNull«[0]; - // $TEST$ no error r"Expected type 'List' or 'Map' but got .*\." »myList«[0]; - // $TEST$ no error r"Expected type 'List' or 'Map' but got .*\." »myMap«[""]; - - // $TEST$ error "Expected type 'List' or 'Map' but got '$unknown'." »unresolved«[0]; } + +// Strict checking of type parameter types +class MyClass( + p1: T, + + // $TEST$ error "Expected type 'List' or 'Map' but got 'T'." + p2: Any? = »p1«[0], +)