Skip to content

Commit

Permalink
feat: ignore parameter names when type checking arguments (#1052)
Browse files Browse the repository at this point in the history
Closes #1046

### Summary of Changes

Names of parameters of passed lambdas no longer matter for type
checking. The following code is now legal:

```
@pure fun f(callback: (p: Int) -> ())

pipeline myPipeline {
    f((q) {});
    f((q) -> 1);
}
```
  • Loading branch information
lars-reimann authored Apr 15, 2024
1 parent f6330a1 commit a1e6717
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export class SafeDsTypeChecker {
const otherEntry = other.inputType.entries[i]!;

// Names must match
if (typeEntry.name !== otherEntry.name) {
if (!options.ignoreParameterNames && typeEntry.name !== otherEntry.name) {
return false;
}

Expand Down Expand Up @@ -438,9 +438,14 @@ export class SafeDsTypeChecker {
/**
* Options for {@link SafeDsTypeChecker.isSubtypeOf} and {@link SafeDsTypeChecker.isSupertypeOf}.
*/
interface TypeCheckOptions {
export interface TypeCheckOptions {
/**
* Whether to ignore type parameters when comparing class types.
*/
ignoreTypeParameters?: boolean;

/**
* Whether to ignore parameter names when comparing callable types.
*/
ignoreParameterNames?: boolean;
}
2 changes: 1 addition & 1 deletion packages/safe-ds-lang/src/language/validation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const callArgumentTypesMustMatchParameterTypes = (services: SafeDsService
const argumentType = typeComputer.computeType(argument).substituteTypeParameters(substitutions);
const parameterType = typeComputer.computeType(parameter).substituteTypeParameters(substitutions);

if (!typeChecker.isSubtypeOf(argumentType, parameterType)) {
if (!typeChecker.isSubtypeOf(argumentType, parameterType, { ignoreParameterNames: true })) {
accept('error', `Expected type '${parameterType}' but got '${argumentType}'.`, {
node: argument,
property: 'value',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
} from '../../../../src/language/typing/model.js';
import { getNodeOfType } from '../../../helpers/nodeFinder.js';
import { AstUtils } from 'langium';
import { TypeCheckOptions } from '../../../../src/language/typing/safe-ds-type-checker.js';

const services = (await createSafeDsServices(NodeFileSystem)).SafeDs;
const coreTypes = services.typing.CoreTypes;
Expand Down Expand Up @@ -137,6 +138,14 @@ const basic = async (): Promise<IsSubOrSupertypeOfTest[]> => {
type2: callableType4,
expected: false,
},
{
type1: callableType3,
type2: callableType4,
options: {
ignoreParameterNames: true,
},
expected: true,
},
{
type1: callableType3,
type2: callableType5,
Expand Down Expand Up @@ -1229,15 +1238,15 @@ const typeVariables = async (): Promise<IsSubOrSupertypeOfTest[]> => {
describe('SafeDsTypeChecker', async () => {
const testCases = (await Promise.all([basic(), classTypesWithTypeParameters(), typeVariables()])).flat();

describe.each(testCases)('isSubtypeOf', ({ type1, type2, expected }) => {
it(`should check whether ${type1} a subtype of ${type2}`, () => {
expect(typeChecker.isSubtypeOf(type1, type2)).toBe(expected);
describe.each(testCases)('isSubtypeOf', ({ type1, type2, options, expected }) => {
it(`should check whether ${type1} a subtype of ${type2} ${options ? `with options ${JSON.stringify(options)}` : ''}`, () => {
expect(typeChecker.isSubtypeOf(type1, type2, options)).toBe(expected);
});
});

describe.each(testCases)('isSupertypeOf', ({ type2, type1, expected }) => {
it(`should check whether ${type2} a supertype of ${type1}`, () => {
expect(typeChecker.isSupertypeOf(type2, type1)).toBe(expected);
describe.each(testCases)('isSupertypeOf', ({ type2, type1, options, expected }) => {
it(`should check whether ${type2} a supertype of ${type1} ${options ? `with options ${JSON.stringify(options)}` : ''}`, () => {
expect(typeChecker.isSupertypeOf(type2, type1, options)).toBe(expected);
});
});
});
Expand All @@ -1263,6 +1272,11 @@ interface IsSubOrSupertypeOfTest {
*/
type2: Type;

/**
* Options for type checking.
*/
options?: TypeCheckOptions;

/**
* Whether {@link type1} is expected to be assignable to {@link type2}.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package tests.validation.types.checking.arguments.parameterNamesOfCallableTypes

@Pure fun f(callback: (p: Int) -> (r: Int))

segment mySegment() {
// $TEST$ no error r"Expected type .* but got .*\."
f(»(q) {
yield s = 1;
}«);

// $TEST$ no error r"Expected type .* but got .*\."
f(»(q) -> 1«);
}

0 comments on commit a1e6717

Please sign in to comment.