Skip to content
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
178 changes: 165 additions & 13 deletions pnc/src/ExprBinder.pn
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ class ExprBinder(
expectedType: Type,
scope: Scope
): BoundExpression = {
val lhs = bindLHS(expr.left, scope)
val lhs = inferLHS(expr.left, scope)
val rhs = infer(expr.right, scope)
Tuple2(lhs, rhs) match {
case Tuple2(Result.Error(error), _) =>
Expand Down Expand Up @@ -308,15 +308,76 @@ class ExprBinder(
}

def checkCall(
expr: Expression.Call,
node: Expression.Call,
expectedType: Type,
scope: Scope
): BoundExpression = {
inferCall(expr, scope) match {
case Result.Error(value) => value
case Result.Success(value) =>
val inferred = convertLHSToExpression(value)
subsume(inferred, expectedType)
// For call expressions, we can use the expected type to improve type inference
// for generic functions
inferLHS(node.name, scope) match {
case Result.Error(error) => error
case Result.Success(function) =>
val exprs = fromExpressionList(node.arguments.expressions)
val args = bindExpressions(exprs, scope)

// Try to use expected type for better generic inference
checkCallLHS(
function,
args,
expectedType,
scope
) match {
case Result.Error(value) => value
case Result.Success(value) =>
val inferred = convertLHSToExpression(value)
subsume(inferred, expectedType)
}
}
}

def checkCallLHS(
function: BoundLeftHandSide,
args: List[BoundExpression],
expectedType: Type,
scope: Scope
): Result[BoundExpression.Error, BoundLeftHandSide] = {
val functionType = getLHSType(function)
val argTypes = binder.getTypes(args)

if (typesWithError(List.Cons(functionType, argTypes))) {
Result.Error(
BoundExpression.Error(
"Cannot bind call expression because of type errors"
)
)
} else {
functionType match {
case func: Type.Function =>
// Non-generic function: use normal binding
bindFunctionCall(function, func, args, scope) match {
case Result.Error(value) => Result.Error(value)
case Result.Success(value) =>
Result.Success(BoundLeftHandSide.Call(value))
}

case gf: Type.GenericFunction =>
// Generic function: use expected type to help infer type arguments!
checkGenericFunctionCall(
function,
gf,
args,
expectedType,
scope
) match {
case Result.Error(value) => Result.Error(value)
case Result.Success(value) =>
Result.Success(BoundLeftHandSide.Call(value))
}

case _ =>
// Fall back to regular inference
inferCall(function, args, scope)
}
}
}

Expand Down Expand Up @@ -528,7 +589,7 @@ class ExprBinder(
}
}

def bindLHS(
def inferLHS(
expr: Expression,
scope: Scope
): Result[BoundExpression.Error, BoundLeftHandSide] = {
Expand Down Expand Up @@ -690,7 +751,7 @@ class ExprBinder(
node: Expression.Assignment,
scope: Scope
): BoundExpression = {
bindLHS(node.left, scope) match {
inferLHS(node.left, scope) match {
case Result.Error(value) => value
case Result.Success(lhs) =>
getLHSType(lhs) match {
Expand Down Expand Up @@ -800,17 +861,17 @@ class ExprBinder(
BoundExpression.Error,
BoundLeftHandSide
] = {
bindLHS(node.name, scope) match {
inferLHS(node.name, scope) match {
case Result.Error(error) => Result.Error(error)

case Result.Success(function) =>
val exprs = fromExpressionList(node.arguments.expressions)
val args = bindExpressions(exprs, scope)
bindCall(function, args, scope)
inferCall(function, args, scope)
}
}

def bindCall(
def inferCall(
function: BoundLeftHandSide,
args: List[BoundExpression],
scope: Scope
Expand Down Expand Up @@ -1184,6 +1245,97 @@ class ExprBinder(
}
}

/** Bind a generic function call using BOTH argument types AND expected return
* type for improved type inference (bidirectional typing)
*/
def checkGenericFunctionCall(
function: BoundLeftHandSide,
genericFunctionType: Type.GenericFunction,
args: List[BoundExpression],
expectedType: Type,
scope: Scope
): Result[BoundExpression.Error, BoundExpression.Call] = {
val location = AstUtils.locationOfBoundLeftHandSide(function)

// Infer type arguments using BOTH arguments AND expected return type!
val argTypes = binder.getTypes(args)
val parameterTypes = getParameterTypes(genericFunctionType.parameters)

val inferredTypeArgs =
typeInference.checkTypeArgumentsFromCall(
genericFunctionType.generics,
parameterTypes,
argTypes,
genericFunctionType.returnType, // The declared return type (may contain type vars)
expectedType // The expected type from context
)

// Instantiate the generic function with inferred type arguments
val instantiatedParameterTypes =
typeInference.substituteGenericTypesInParameters(
parameterTypes,
genericFunctionType.generics,
inferredTypeArgs
)

val instantiatedReturnType = typeInference.substituteGenericTypesInType(
genericFunctionType.returnType,
genericFunctionType.generics,
inferredTypeArgs
)

// Check argument count matches
if (instantiatedParameterTypes.length != args.length) {
diagnosticBag.reportArgumentCountMismatch(
location,
instantiatedParameterTypes.length,
args.length
)
Result.Error(
BoundExpression.Error(
"Argument count mismatch: expected " + instantiatedParameterTypes.length +
" but got " + args.length
)
)
} else {
// Bind arguments with instantiated parameter types
val boundArgs =
bindArgumentsToTypes(instantiatedParameterTypes, args, scope)

function match {
case BoundLeftHandSide.Index(expression) => ???
case BoundLeftHandSide.MemberAccess(access) =>
val receiver = if (access.member.isStatic()) {
Option.None
} else {
Option.Some(access.receiver)
}
Result.Success(
BoundExpression.Call(
location,
receiver,
access.member,
inferredTypeArgs,
boundArgs,
instantiatedReturnType
)
)
case BoundLeftHandSide.Variable(location, symbol) =>
Result.Success(
BoundExpression.Call(
location,
Option.None,
symbol,
inferredTypeArgs,
boundArgs,
instantiatedReturnType
)
)
case _ => ???
}
}
}

def bindArgumentsToTypes(
parameterTypes: List[Type],
args: List[BoundExpression],
Expand Down Expand Up @@ -1504,7 +1656,7 @@ class ExprBinder(
): Result[BoundExpression.Error, BoundExpression.MemberAccess] = {
// For member access, the left side doesn't need to be a left-hand side
// It just needs to be a valid expression that produces a value
val leftExpr = bindLHS(node.left, scope)
val leftExpr = inferLHS(node.left, scope)
leftExpr match {
case Result.Error(error) =>
Result.Error(error)
Expand Down
61 changes: 24 additions & 37 deletions pnc/src/TypeInference.pn
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ class TypeInference(binder: Binder) {
parameterTypes: List[Type],
argumentTypes: List[Type]
): List[Type] = {
// Simple type inference: try to match parameter types with argument types
// This is a basic implementation - a full implementation would be more sophisticated
val typeMap = new scala.collection.mutable.HashMap[String, Type]()

// Try to infer from parameter/argument pairs
Expand All @@ -21,6 +19,30 @@ class TypeInference(binder: Binder) {
mapGenericParamsToTypes(genericParams, typeMap)
}

/** Infers type arguments for a generic function call using both argument
* types AND the expected return type (bidirectional inference)
*/
def checkTypeArgumentsFromCall(
genericParams: List[GenericTypeParameter],
parameterTypes: List[Type],
argumentTypes: List[Type],
returnType: Type,
expectedType: Type
): List[Type] = {
val typeMap = new scala.collection.mutable.HashMap[String, Type]()

// First, infer from parameter/argument pairs (same as before)
inferFromParameterArgumentPairs(parameterTypes, argumentTypes, typeMap)

// NEW: Also infer from return type vs expected type
// This helps when the return type contains type variables
// Example: identity<T>(x: T): T called with expected type string
inferTypeFromPair(returnType, expectedType, typeMap)

// Convert to list of types in the order of generic parameters
mapGenericParamsToTypes(genericParams, typeMap)
}

def mapGenericParamsToTypes(
genericParams: List[GenericTypeParameter],
typeMap: scala.collection.mutable.HashMap[String, Type]
Expand Down Expand Up @@ -167,41 +189,6 @@ class TypeInference(binder: Binder) {
}
}

def findTypeArgument(
name: String,
genericParams: List[GenericTypeParameter],
typeArgs: List[Type]
): Option[Type] = {
Tuple2(genericParams, typeArgs) match {
case Tuple2(List.Cons(param, paramTail), List.Cons(typeArg, argTail)) =>
if (param.name == name) Option.Some(typeArg)
else findTypeArgument(name, paramTail, argTail)
case _ => Option.None
}
}

def createSubstitutionMap(
genericParams: List[GenericTypeParameter],
typeArgs: List[Type]
): scala.collection.mutable.HashMap[String, Type] = {
val map = new scala.collection.mutable.HashMap[String, Type]()
fillSubstitutionMap(genericParams, typeArgs, map)
map
}

def fillSubstitutionMap(
genericParams: List[GenericTypeParameter],
typeArgs: List[Type],
map: scala.collection.mutable.HashMap[String, Type]
): Unit = {
Tuple2(genericParams, typeArgs) match {
case Tuple2(List.Cons(param, paramTail), List.Cons(typeArg, argTail)) =>
map.put(param.name, typeArg)
fillSubstitutionMap(paramTail, argTail, map)
case _ => // Lists are different lengths or empty
}
}

def substituteParameterTypes(
params: List[BoundParameter],
typeArgs: List[Type]
Expand Down
Loading