Skip to content

Commit

Permalink
feat: handle type parameter types in type checker (#884)
Browse files Browse the repository at this point in the history
Closes #877

### Summary of Changes

The type checker can now properly check assignability of and to type
parameter types.
  • Loading branch information
lars-reimann authored Feb 12, 2024
1 parent 8776ce0 commit 6b6f738
Show file tree
Hide file tree
Showing 3 changed files with 600 additions and 5 deletions.
26 changes: 22 additions & 4 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 @@ -55,10 +55,21 @@ export class SafeDsTypeChecker {

if (type === UnknownType || other === UnknownType) {
return false;
} else if (type instanceof TypeParameterType || other instanceof TypeParameterType) {
/* c8 ignore next 3 */
// TODO(LR): This must be updated when we work on type parameter constraints.
return true;
} else if (other instanceof TypeParameterType) {
const otherLowerBound = this.typeComputer().computeLowerBound(other);
const otherUpperBound = this.typeComputer().computeUpperBound(other);

if (!(type instanceof TypeParameterType)) {
return this.isAssignableTo(otherLowerBound, type) && this.isAssignableTo(type, otherUpperBound);
}

const typeLowerBound = this.typeComputer().computeLowerBound(type);
const typeUpperBound = this.typeComputer().computeUpperBound(type);

return (
this.isAssignableTo(otherLowerBound, typeLowerBound) &&
this.isAssignableTo(typeUpperBound, otherUpperBound)
);
} else if (other instanceof UnionType) {
return other.possibleTypes.some((it) => this.isAssignableTo(type, it));
}
Expand All @@ -77,6 +88,8 @@ export class SafeDsTypeChecker {
return this.namedTupleTypeIsAssignableTo(type, other);
} else if (type instanceof StaticType) {
return this.staticTypeIsAssignableTo(type, other);
} else if (type instanceof TypeParameterType) {
return this.typeParameterTypeIsAssignableTo(type, other);
} else if (type instanceof UnionType) {
return this.unionTypeIsAssignableTo(type, other);
} /* c8 ignore start */ else {
Expand Down Expand Up @@ -309,6 +322,11 @@ export class SafeDsTypeChecker {
}
}

private typeParameterTypeIsAssignableTo(type: TypeParameterType, other: Type): boolean {
const upperBound = this.typeComputer().computeUpperBound(type);
return this.isAssignableTo(upperBound, other);
}

private unionTypeIsAssignableTo(type: UnionType, other: Type): boolean {
return type.possibleTypes.every((it) => this.isAssignableTo(it, other));
}
Expand Down
85 changes: 85 additions & 0 deletions packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import {
SdsType,
SdsTypeArgument,
SdsTypeParameter,
SdsTypeParameterBound,
} from '../generated/ast.js';
import {
getAssignees,
Expand All @@ -84,6 +85,7 @@ import {
getTypeArguments,
getTypeParameters,
streamBlockLambdaResults,
TypeParameter,
} from '../helpers/nodeProperties.js';
import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js';
import {
Expand Down Expand Up @@ -775,6 +777,89 @@ export class SafeDsTypeComputer {
} /* c8 ignore stop */
}

// -----------------------------------------------------------------------------------------------------------------
// Type parameter bounds
// -----------------------------------------------------------------------------------------------------------------

/**
* Returns the lower bound for the given type parameter type. If no lower bound is specified explicitly, the result
* is `Nothing`. If invalid lower bounds are specified (e.g. because of an unresolved reference or a cycle),
* `$unknown` is returned. The result is simplified as much as possible.
*/
computeLowerBound(type: TypeParameterType): Type {
return this.doComputeLowerBound(type, new Set());
}

private doComputeLowerBound(type: TypeParameterType, visited: Set<SdsTypeParameter>): Type {
// Check for cycles
if (visited.has(type.declaration)) {
return UnknownType;
}
visited.add(type.declaration);

const lowerBounds = TypeParameter.getLowerBounds(type.declaration);
if (isEmpty(lowerBounds)) {
return this.coreTypes.Nothing;
}

const boundType = this.computeLowerBoundType(lowerBounds[0]!);
if (!(boundType instanceof NamedType)) {
return UnknownType;
} else if (!(boundType instanceof TypeParameterType)) {
return boundType;
} else {
return this.doComputeLowerBound(boundType, visited);
}
}

private computeLowerBoundType(node: SdsTypeParameterBound): Type {
if (node.operator === 'super') {
return this.computeType(node.rightOperand);
} else {
return this.computeType(node.leftOperand?.ref);
}
}

/**
* Returns the upper bound for the given type parameter type. If no upper bound is specified explicitly, the result
* is `Any?`. If invalid upper bounds are specified, but are invalid (e.g. because of an unresolved reference or a
* cycle), `$unknown` is returned. The result is simplified as much as possible.
*/
computeUpperBound(type: TypeParameterType): Type {
const result = this.doComputeUpperBound(type, new Set());
return result.updateNullability(result.isNullable || type.isNullable);
}

private doComputeUpperBound(type: TypeParameterType, visited: Set<SdsTypeParameter>): Type {
// Check for cycles
if (visited.has(type.declaration)) {
return UnknownType;
}
visited.add(type.declaration);

const upperBounds = TypeParameter.getUpperBounds(type.declaration);
if (isEmpty(upperBounds)) {
return this.coreTypes.AnyOrNull;
}

const boundType = this.computeUpperBoundType(upperBounds[0]!);
if (!(boundType instanceof NamedType)) {
return UnknownType;
} else if (!(boundType instanceof TypeParameterType)) {
return boundType;
} else {
return this.doComputeUpperBound(boundType, visited);
}
}

private computeUpperBoundType(node: SdsTypeParameterBound): Type {
if (node.operator === 'sub') {
return this.computeType(node.rightOperand);
} else {
return this.computeType(node.leftOperand?.ref);
}
}

// -----------------------------------------------------------------------------------------------------------------
// Lowest common supertype
// -----------------------------------------------------------------------------------------------------------------
Expand Down
Loading

0 comments on commit 6b6f738

Please sign in to comment.