Skip to content

Commit

Permalink
feat: type checking for callable types
Browse files Browse the repository at this point in the history
  • Loading branch information
lars-reimann committed Nov 4, 2023
1 parent 756aebc commit 89c16c7
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AstNode, getContainerOfType, Stream, stream } from 'langium';
import {
isSdsAnnotation,
isSdsArgumentList,
Expand Down Expand Up @@ -57,7 +58,6 @@ import {
SdsTypeParameter,
SdsTypeParameterList,
} from '../generated/ast.js';
import { AstNode, getContainerOfType, Stream, stream } from 'langium';

// -------------------------------------------------------------------------------------------------
// Checks
Expand Down
85 changes: 53 additions & 32 deletions packages/safe-ds-lang/src/language/typing/safe-ds-type-checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,39 +68,60 @@ export class SafeDsTypeChecker {
}

private callableTypeIsAssignableTo(type: CallableType, other: Type): boolean {
// return when (val unwrappedOther = unwrapVariadicType(other)) {
// is CallableType -> {
// // TODO: We need to compare names of parameters & results and can allow additional optional parameters
//
// // Sizes must match (too strict requirement -> should be loosened later)
// if (this.parameters.size != unwrappedOther.parameters.size || this.results.size != this.results.size) {
// return false
// }
//
// // Actual parameters must be supertypes of expected parameters (contravariance)
// this.parameters.zip(unwrappedOther.parameters).forEach { (thisParameter, otherParameter) ->
// if (!otherParameter.isSubstitutableFor(thisParameter)) {
// return false
// }
// }
//
// // Expected results must be subtypes of expected results (covariance)
// this.results.zip(unwrappedOther.results).forEach { (thisResult, otherResult) ->
// if (!thisResult.isSubstitutableFor(otherResult)) {
// return false
// }
// }
//
// true
// }
// is ClassType -> {
// unwrappedOther.sdsClass.qualifiedNameOrNull() == StdlibClasses.Any
// }
// else -> false
// }
// }
if (other instanceof ClassType) {
return other.declaration === this.builtinClasses.Any;
} else if (other instanceof CallableType) {
// Must accept at least as many parameters and produce at least as many results
if (type.inputType.length < other.inputType.length || type.outputType.length < other.outputType.length) {
return false;
}

return type.equals(other);
// Check expected parameters
for (let i = 0; i < other.inputType.length; i++) {
const typeEntry = type.inputType.entries[i];
const otherEntry = other.inputType.entries[i];

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

// Types must be contravariant
if (!this.isAssignableTo(otherEntry.type, typeEntry.type)) {
return false;
}
}

// Additional parameters must be optional
for (let i = other.inputType.length; i < type.inputType.length; i++) {
const typeEntry = type.inputType.entries[i];
if (!typeEntry.declaration?.defaultValue) {
return false;
}
}

// Check expected results
for (let i = 0; i < other.outputType.length; i++) {
const typeEntry = type.outputType.entries[i];
const otherEntry = other.outputType.entries[i];

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

// Types must be covariant
if (!this.isAssignableTo(typeEntry.type, otherEntry.type)) {
return false;
}
}

// Additional results are OK

return true;
} else {
return false;
}
}

private classTypeIsAssignableTo(type: ClassType, other: Type): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,16 @@ const typeChecker = services.types.TypeChecker;
const typeComputer = services.types.TypeComputer;

const code = `
fun func() -> ()
fun func1() -> ()
fun func2(p: Int = 0) -> ()
fun func3(p: Int) -> ()
fun func4(r: Int) -> ()
fun func5(p: Any) -> ()
fun func6(p: String) -> ()
fun func7() -> (r: Int)
fun func8() -> (s: Int)
fun func9() -> (r: Any)
fun func10() -> (r: String)
class Class1
class Class2 sub Class1
Expand All @@ -48,8 +57,17 @@ const code = `
enum Enum2
`;
const module = await getNodeOfType(services, code, isSdsModule);
const func = getModuleMembers(module).find(isSdsFunction)!;
const callableType = typeComputer.computeType(func);
const functions = getModuleMembers(module).filter(isSdsFunction);
const callableType1 = typeComputer.computeType(functions[0]);
const callableType2 = typeComputer.computeType(functions[1]);
const callableType3 = typeComputer.computeType(functions[2]);
const callableType4 = typeComputer.computeType(functions[3]);
const callableType5 = typeComputer.computeType(functions[4]);
const callableType6 = typeComputer.computeType(functions[5]);
const callableType7 = typeComputer.computeType(functions[6]);
const callableType8 = typeComputer.computeType(functions[7]);
const callableType9 = typeComputer.computeType(functions[8]);
const callableType10 = typeComputer.computeType(functions[9]);

const classes = getModuleMembers(module).filter(isSdsClass);
const class1 = classes[0];
Expand All @@ -73,8 +91,93 @@ const enumVariantType2 = typeComputer.computeType(enumVariant2);

describe('SafeDsTypeChecker', async () => {
const testCases: IsAssignableToTest[] = [
// Callable type to X
// TODO
{
type1: callableType1,
type2: callableType1,
expected: true,
},
{
type1: callableType2,
type2: callableType1,
expected: true,
},
{
type1: callableType1,
type2: callableType2,
expected: false,
},
{
type1: callableType3,
type2: callableType1,
expected: false,
},
{
type1: callableType3,
type2: callableType4,
expected: false,
},
{
type1: callableType3,
type2: callableType5,
expected: false,
},
{
type1: callableType5,
type2: callableType3,
expected: true,
},
{
type1: callableType6,
type2: callableType3,
expected: false,
},
{
type1: callableType7,
type2: callableType1,
expected: true,
},
{
type1: callableType1,
type2: callableType7,
expected: false,
},
{
type1: callableType8,
type2: callableType7,
expected: false,
},
{
type1: callableType9,
type2: callableType7,
expected: false,
},
{
type1: callableType7,
type2: callableType9,
expected: true,
},
{
type1: callableType10,
type2: callableType7,
expected: false,
},
// Callable type to class type
{
type1: callableType1,
type2: coreTypes.Any,
expected: true,
},
{
type1: callableType1,
type2: coreTypes.AnyOrNull,
expected: true,
},
// Callable type to other
{
type1: callableType1,
type2: enumType1,
expected: false,
},
// Class type to class type
{
type1: classType1,
Expand Down

0 comments on commit 89c16c7

Please sign in to comment.