Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Continuing to support <unknown> type #551

Merged
merged 1 commit into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 47 additions & 14 deletions projects/compiler/example.abra
Original file line number Diff line number Diff line change
@@ -1,22 +1,55 @@
import "./example2" as ex2

func bar(a: Abc): Int {
val b = -a
val c = !a
val d = "hello $a"
val e = (a)
val f = r

val g = ex2?.bar
val h = ex2.bar
// unary
val unary1 = -a
val unary2 = !a

val binary1 = a + 1
val binary2 = a - 1
val binary3 = a * 1
val binary4 = a / 1
val binary5 = a % 1
val binary6 = a > 1
val binary7 = a >= 1
val binary8 = a < 1
val binary9 = a <= 1

val stringInterpolation = "hello $a"

val grouped = (a)

val identifier = r

// accessor
val nonOptModuleAccess = ex2?.bar
val modAccess = ex2.bar
val arr = [a.abc, a.def]
val i = arr.size.absVal
val j = [1, 2, 3][0].abs
val k = arr.withCapacity
// val g = ex2.bar()
val fieldAccess = arr.size.absVal
val optFieldAccess = [1, 2, 3][0].abs
val staticFieldAccess = arr.withCapacity

// invocation
val invocationModuleAccess = ex2.bar()
val invocationNoSuchFunction = what()
val invocationNoSuchInstanceMethod = arr.foo()
val invocationNoSuchStaticMethod = Array.foo()

val x = a + 1
return x
val indexing1 = huh[0]
val indexing2 = arr.huh[0]

val try1 = try huh
val try2 = try arr.huh

return a + 1
}

val abc = bar(123)

func baz(): Abc? {
val try1 = try [1, 2, 3][0]
val try2 = try huh
val try3 = try arr.huh
}

val def = baz()
81 changes: 53 additions & 28 deletions projects/compiler/src/typechecker.abra
Original file line number Diff line number Diff line change
Expand Up @@ -1366,13 +1366,16 @@ type TypeError {
InvalidTryLocationReason.NotWithinFunction => {
lines.push("Try expressions can only be used inside of function bodies")
}
InvalidTryLocationReason.InvalidFunctionReturnType(fnLabel, tryType, returnType, isResult) => {
InvalidTryLocationReason.InvalidFunctionReturnType(fnLabel, tryType, returnType, kind) => {
lines.push("The containing function '${fnLabel.name}' has return type '${returnType.repr()}', which is incompatible with the try expression's type '${tryType.repr()}'.")
lines.push(self._getCursorLine(fnLabel.position, contents))
if isResult {
lines.push("To be compatible, '${fnLabel.name}' must return a Result whose error type matches that of the try expression")
} else {
lines.push("To be compatible, '${fnLabel.name}' must return an Option type")
match kind {
InvalidFunctionReturnTypeKind.IsResult => {
lines.push("To be compatible, '${fnLabel.name}' must return a Result whose error type matches that of the try expression")
}
InvalidFunctionReturnTypeKind.IsOption => {
lines.push("To be compatible, '${fnLabel.name}' must return an Option type")
}
}
}
}
Expand Down Expand Up @@ -1448,9 +1451,15 @@ enum InvalidDestructuringReason {
InvalidTupleArity(expected: Int, given: Int)
}

enum InvalidFunctionReturnTypeKind {
IsResult
IsOption
IsNotTryable
}

enum InvalidTryLocationReason {
NotWithinFunction
InvalidFunctionReturnType(fnLabel: Label, tryType: Type, returnType: Type, isResult: Bool)
InvalidFunctionReturnType(fnLabel: Label, tryType: Type, returnType: Type, kind: InvalidFunctionReturnTypeKind)
}

enum TypeErrorKind {
Expand Down Expand Up @@ -2404,7 +2413,10 @@ pub type Typechecker {
}

val returnType = if node.returnTypeAnnotation |typeAnn| {
try self.resolveTypeIdentifier(typeAnn)
try self.resolveTypeIdentifier(typeAnn) else |err| {
self.currentModule.addTypeError(err)
Type(kind: TypeKind.CouldNotDetermine)
}
} else {
Type(kind: TypeKind.PrimitiveUnit)
}
Expand Down Expand Up @@ -2585,7 +2597,7 @@ pub type Typechecker {
val prevFn = self.currentFunction
self.currentFunction = Some(fn)

val hasReturnValue = fn.returnType.kind != TypeKind.PrimitiveUnit
val hasReturnValue = fn.returnType.kind != TypeKind.PrimitiveUnit && fn.returnType.kind != TypeKind.CouldNotDetermine
if hasReturnValue && body.isEmpty() {
if !fn.decorators.find(dec => dec.label.name == "Stub" || dec.label.name == "Intrinsic" || dec.label.name == "CBinding")
return Err(TypeError(position: fn.label.position, kind: TypeErrorKind.ReturnTypeMismatch(Some(fn.label.name), fn.returnType, None)))
Expand Down Expand Up @@ -3420,10 +3432,10 @@ pub type Typechecker {
return Ok(TypedAstNode(token: token, ty: Type(kind: TypeKind.PrimitiveUnit), kind: TypedAstNodeKind.Assignment(mode, op, typedExpr)))
}
AssignmentMode.Indexing(targetExpr, indexExpr) => {
val typedLhs = try self._typecheckIndexing(token, targetExpr, IndexingMode.Single(indexExpr), None)
val typedLhs = try self.typecheckIndexing(token, targetExpr, IndexingMode.Single(indexExpr), None)
val typedIndexingNode = match typedLhs.kind {
TypedAstNodeKind.Indexing(node) => node
_ => unreachable("_typecheckIndexing returned unexpected TypedAstNodeKind")
_ => unreachable("typecheckIndexing returned unexpected TypedAstNodeKind")
}
val assignmentTy = self._typeIsOption(typedLhs.ty) ?: typedLhs.ty

Expand Down Expand Up @@ -4068,16 +4080,16 @@ pub type Typechecker {
}
AstNodeKind.Identifier(kind) => self.typecheckIdentifier(token, kind, typeHint)
AstNodeKind.Accessor(node) => self.typecheckAccessor(token, node, typeHint)
AstNodeKind.Invocation(node) => self._typecheckInvocation(token, node, typeHint)
AstNodeKind.Array(items) => self._typecheckArray(token, items, typeHint)
AstNodeKind.Set(items) => self._typecheckSet(token, items, typeHint)
AstNodeKind.Map(items) => self._typecheckMap(token, items, typeHint)
AstNodeKind.Tuple(items) => self._typecheckTuple(token, items, typeHint)
AstNodeKind.Indexing(expr, index) => self._typecheckIndexing(token, expr, index, typeHint)
AstNodeKind.Invocation(node) => self.typecheckInvocation(token, node, typeHint)
AstNodeKind.Array(items) => self.typecheckArray(token, items, typeHint)
AstNodeKind.Set(items) => self.typecheckSet(token, items, typeHint)
AstNodeKind.Map(items) => self.typecheckMap(token, items, typeHint)
AstNodeKind.Tuple(items) => self.typecheckTuple(token, items, typeHint)
AstNodeKind.Indexing(expr, index) => self.typecheckIndexing(token, expr, index, typeHint)
AstNodeKind.Lambda(value) => self._typecheckLambda(token, value, typeHint)
AstNodeKind.If(condition, conditionBinding, ifBlock, elseBlock) => self._typecheckIf(token, condition, conditionBinding, ifBlock, elseBlock, typeHint)
AstNodeKind.Match(subject, cases) => self._typecheckMatch(token, subject, cases, typeHint)
AstNodeKind.Try(expr, elseClause) => self._typecheckTry(token, expr, elseClause, typeHint)
AstNodeKind.Try(expr, elseClause) => self.typecheckTry(token, expr, elseClause, typeHint)
_ => unreachable("all other node types should have already been handled")
}

Expand Down Expand Up @@ -4547,6 +4559,8 @@ pub type Typechecker {

val path: AccessorPathSegment[] = []
var ty = typedRoot.ty
if ty.kind == TypeKind.CouldNotDetermine return Ok(TypedAstNode(token: token, ty: Type(kind: TypeKind.CouldNotDetermine), kind: TypedAstNodeKind.Placeholder))

var seenOptSafeDot = false
for (token, label), idx in accessorPath {
val isOptSafe = token.kind == TokenKind.QuestionDot
Expand Down Expand Up @@ -4670,13 +4684,15 @@ pub type Typechecker {
Ok(TypedAstNode(token: token, ty: ty, kind: TypedAstNodeKind.Accessor(typedRoot, path, finalField)))
}

func _typecheckInvocation(self, token: Token, node: InvocationAstNode, typeHint: Type?): Result<TypedAstNode, TypeError> {
func typecheckInvocation(self, token: Token, node: InvocationAstNode, typeHint: Type?): Result<TypedAstNode, TypeError> {
self.isStructOrEnumValueAllowed = true
self.isEnumContainerValueAllowed = true
val invokee = try self._typecheckExpression(node.invokee, None)
self.isEnumContainerValueAllowed = false
self.isStructOrEnumValueAllowed = false

if invokee.ty.kind == TypeKind.CouldNotDetermine return Ok(TypedAstNode(token: token, ty: Type(kind: TypeKind.CouldNotDetermine), kind: TypedAstNodeKind.Placeholder))

match invokee.kind {
TypedAstNodeKind.Identifier(name, variable, _, _) => {
match variable.alias {
Expand Down Expand Up @@ -4998,7 +5014,7 @@ pub type Typechecker {
Ok(TypedAstNode(token: token, ty: returnType, kind: TypedAstNodeKind.Invocation(typedInvokee, typedArguments, resolvedGenerics)))
}

func _typecheckArray(self, token: Token, items: AstNode[], typeHint: Type?): Result<TypedAstNode, TypeError> {
func typecheckArray(self, token: Token, items: AstNode[], typeHint: Type?): Result<TypedAstNode, TypeError> {
var innerType: Type? = None
if typeHint |hint| {
if self._typeAsInstance1(hint, self.project.preludeArrayStruct) |inner| {
Expand Down Expand Up @@ -5038,7 +5054,7 @@ pub type Typechecker {
Ok(TypedAstNode(token: token, ty: ty, kind: TypedAstNodeKind.Array(typedItems)))
}

func _typecheckSet(self, token: Token, items: AstNode[], typeHint: Type?): Result<TypedAstNode, TypeError> {
func typecheckSet(self, token: Token, items: AstNode[], typeHint: Type?): Result<TypedAstNode, TypeError> {
var innerType: Type? = None
if typeHint |hint| {
if self._typeAsInstance1(hint, self.project.preludeSetStruct) |inner| {
Expand Down Expand Up @@ -5078,7 +5094,7 @@ pub type Typechecker {
Ok(TypedAstNode(token: token, ty: ty, kind: TypedAstNodeKind.Set(typedItems)))
}

func _typecheckMap(self, token: Token, items: (AstNode, AstNode)[], typeHint: Type?): Result<TypedAstNode, TypeError> {
func typecheckMap(self, token: Token, items: (AstNode, AstNode)[], typeHint: Type?): Result<TypedAstNode, TypeError> {
var keyTy: Type? = None
var valTy: Type? = None
if typeHint |hint| {
Expand Down Expand Up @@ -5138,7 +5154,7 @@ pub type Typechecker {
Ok(TypedAstNode(token: token, ty: ty, kind: TypedAstNodeKind.Map(typedItems)))
}

func _typecheckTuple(self, token: Token, items: AstNode[], typeHint: Type?): Result<TypedAstNode, TypeError> {
func typecheckTuple(self, token: Token, items: AstNode[], typeHint: Type?): Result<TypedAstNode, TypeError> {
var typeHints: Type[]? = None
if typeHint |hint| {
match hint.kind {
Expand Down Expand Up @@ -5169,8 +5185,10 @@ pub type Typechecker {
Ok(TypedAstNode(token: token, ty: ty, kind: TypedAstNodeKind.Tuple(typedItems)))
}

func _typecheckIndexing(self, token: Token, expr: AstNode, indexMode: IndexingMode<AstNode>, typeHint: Type?): Result<TypedAstNode, TypeError> {
func typecheckIndexing(self, token: Token, expr: AstNode, indexMode: IndexingMode<AstNode>, typeHint: Type?): Result<TypedAstNode, TypeError> {
val typedExpr = try self._typecheckExpression(expr, None)
if typedExpr.ty.kind == TypeKind.CouldNotDetermine return Ok(TypedAstNode(token: token, ty: Type(kind: TypeKind.CouldNotDetermine), kind: TypedAstNodeKind.Placeholder))

if self._typeIsOption(typedExpr.ty) {
return Err(TypeError(position: typedExpr.token.position, kind: TypeErrorKind.IllegalIndexableType(ty: typedExpr.ty, isRange: false)))
}
Expand Down Expand Up @@ -5325,7 +5343,7 @@ pub type Typechecker {
Ok(TypedAstNode(token: token, ty: lambdaTy, kind: TypedAstNodeKind.Lambda(fn, typeHint)))
}

func _typecheckTry(self, token: Token, expr: AstNode, elseClause: (Token, BindingPattern?, AstNode[])?, typeHint: Type?): Result<TypedAstNode, TypeError> {
func typecheckTry(self, token: Token, expr: AstNode, elseClause: (Token, BindingPattern?, AstNode[])?, typeHint: Type?): Result<TypedAstNode, TypeError> {
// TODO: support top-level try (the error case would just exit the program)
val currentFn = try self.currentFunction else return Err(TypeError(position: token.position, kind: TypeErrorKind.InvalidTryLocation(InvalidTryLocationReason.NotWithinFunction)))

Expand All @@ -5342,12 +5360,19 @@ pub type Typechecker {
} else if elseClause {
val typedExpr = try self._typecheckExpression(expr, None)
(typedExpr, None)
} else if currentFn.returnType.kind == TypeKind.CouldNotDetermine {
return Ok(TypedAstNode(token: token, ty: Type(kind: TypeKind.CouldNotDetermine), kind: TypedAstNodeKind.Placeholder))
} else {
val typedExpr = try self._typecheckExpression(expr, None)
val isResult = !!self._typeIsResult(typedExpr.ty)
return Err(TypeError(position: token.position, kind: TypeErrorKind.InvalidTryLocation(InvalidTryLocationReason.InvalidFunctionReturnType(fnLabel: currentFn.label, tryType: typedExpr.ty, returnType: currentFn.returnType, isResult: isResult))))

if typedExpr.ty.kind == TypeKind.CouldNotDetermine return Ok(TypedAstNode(token: token, ty: Type(kind: TypeKind.CouldNotDetermine), kind: TypedAstNodeKind.Placeholder))

val kind = if self._typeIsResult(typedExpr.ty) InvalidFunctionReturnTypeKind.IsResult else InvalidFunctionReturnTypeKind.IsOption
return Err(TypeError(position: token.position, kind: TypeErrorKind.InvalidTryLocation(InvalidTryLocationReason.InvalidFunctionReturnType(fnLabel: currentFn.label, tryType: typedExpr.ty, returnType: currentFn.returnType, kind: kind))))
}

if typedExpr.ty.kind == TypeKind.CouldNotDetermine return Ok(TypedAstNode(token: token, ty: Type(kind: TypeKind.CouldNotDetermine), kind: TypedAstNodeKind.Placeholder))

val (tryValType, typedElseClause) = if elseClause |(elseToken, bindingPattern, elseBlock)| {
val isStatement = if typeHint |hint| hint.kind == TypeKind.PrimitiveUnit else false

Expand Down Expand Up @@ -5413,11 +5438,11 @@ pub type Typechecker {
exprOkType
}
} else {
return Err(TypeError(position: token.position, kind: TypeErrorKind.InvalidTryLocation(InvalidTryLocationReason.InvalidFunctionReturnType(fnLabel: currentFn.label, tryType: typedExpr.ty, returnType: currentFn.returnType, isResult: true))))
return Err(TypeError(position: token.position, kind: TypeErrorKind.InvalidTryLocation(InvalidTryLocationReason.InvalidFunctionReturnType(fnLabel: currentFn.label, tryType: typedExpr.ty, returnType: currentFn.returnType, kind: InvalidFunctionReturnTypeKind.IsResult))))
}
} else if self._typeIsOption(typedExpr.ty) |inner| {
if returnTypeResultErr {
return Err(TypeError(position: token.position, kind: TypeErrorKind.InvalidTryLocation(InvalidTryLocationReason.InvalidFunctionReturnType(fnLabel: currentFn.label, tryType: typedExpr.ty, returnType: currentFn.returnType, isResult: false))))
return Err(TypeError(position: token.position, kind: TypeErrorKind.InvalidTryLocation(InvalidTryLocationReason.InvalidFunctionReturnType(fnLabel: currentFn.label, tryType: typedExpr.ty, returnType: currentFn.returnType, kind: InvalidFunctionReturnTypeKind.IsOption))))
} else {
inner
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,3 @@ Unknown member 'bogus'
There's no exported value named 'bogus' in module aliased as 'a' at:
| import "./_exports" as a
^

Error at %TEST_DIR%/typechecker/import/error_alias_unknown_import.abra:3:2
Cannot invoke target as function
| a.bogus()
^
Type '<unknown>' is not callable
Original file line number Diff line number Diff line change
@@ -1,22 +1,55 @@
import "./_exports" as ex

func bar(a: Abc): Int {
val b = -a
val c = !a
val d = "hello $a"
val e = (a)
val f = r

val g = ex?.bar
val h = ex.bar
// unary
val unary1 = -a
val unary2 = !a

val binary1 = a + 1
val binary2 = a - 1
val binary3 = a * 1
val binary4 = a / 1
val binary5 = a % 1
val binary6 = a > 1
val binary7 = a >= 1
val binary8 = a < 1
val binary9 = a <= 1

val stringInterpolation = "hello $a"

val grouped = (a)

val identifier = r

// accessor
val nonOptModuleAccess = ex?.bar
val modAccess = ex.bar
val arr = [a.abc, a.def]
val i = arr.size.absVal
val j = [1, 2, 3][0].abs
val k = arr.withCapacity
// val g = ex2.bar()
val fieldAccess = arr.size.absVal
val optFieldAccess = [1, 2, 3][0].abs
val staticFieldAccess = arr.withCapacity

// invocation
val invocationModuleAccess = ex.bar()
val invocationNoSuchFunction = what()
val invocationNoSuchInstanceMethod = arr.foo()
val invocationNoSuchStaticMethod = Array.foo()

val x = a + 1
return x
val indexing1 = huh[0]
val indexing2 = arr.huh[0]

val try1 = try huh
val try2 = try arr.huh

return a + 1
}

val abc = bar(123)

func baz(): Abc? {
val try1 = try [1, 2, 3][0]
val try2 = try huh
val try3 = try arr.huh
}

val def = baz()
Loading