public override VoidObject VisitVarStmt(Stmt.Var stmt) { bool sanityCheckFailed = false; // Sanity check the input to ensure that we don't get NullReferenceExceptions later on if (stmt.TypeReference == null) { TypeValidationErrorCallback(new TypeValidationError( stmt.Name, $"Internal compiler error: {stmt.Name.Lexeme} is missing a TypeReference" )); sanityCheckFailed = true; } if (stmt.Initializer != null && !stmt.Initializer.TypeReference.IsResolved) { TypeValidationErrorCallback(new TypeValidationError( stmt.Name, $"Internal compiler error: var {stmt.Name.Lexeme} initializer {stmt.Initializer} inference has not been attempted" )); sanityCheckFailed = true; } if (sanityCheckFailed) { return(VoidObject.Void); } if (stmt.TypeReference !.IsResolved) { if (stmt.Initializer != null) { if (stmt.TypeReference.IsNullObject && stmt.Initializer.TypeReference.IsNullObject) { // TODO: Use stmt.Initializer.Token here instead of stmt.name, #189 TypeValidationErrorCallback(new TypeValidationError( stmt.Name, "Cannot assign null to an implicitly typed local variable" )); } else if (!TypeCoercer.CanBeCoercedInto(stmt.TypeReference, stmt.Initializer.TypeReference, ((stmt.Initializer as Expr.Literal)?.Value as INumericLiteral)?.BitsUsed)) { // TODO: Use stmt.Initializer.Token here instead of stmt.name, #189 TypeValidationErrorCallback(new TypeValidationError( stmt.Name, $"Cannot assign {stmt.Initializer.TypeReference.ClrType.ToTypeKeyword()} to {stmt.TypeReference.ClrType.ToTypeKeyword()} variable" )); } else if (stmt.Initializer.TypeReference.IsNullObject) { // TODO: Use stmt.Initializer.Token here instead of stmt.name, #189 compilerWarningCallback(new CompilerWarning("Initializing variable to null detected", stmt.Name, WarningType.NULL_USAGE)); } } }
private void VisitCallExprForGetCallee(Expr.Call call, Expr.Get get) { string methodName = get.Name.Lexeme; if (get.Methods.Length == 0) { if (!get.Object.TypeReference.IsResolved) { // This is a bit of an oddball, but... We get here when an attempt is made to call a method on some // undefined type. (`Foo.do_stuff`) // // Now, this is a compile-time error, but the problem is that it's handled by this class itself; // encountering this error will not abort the tree traversal so we must avoid breaking it. } else { // This is even more odd, but we must ensure that we have well-defined semantics in the weird case // where this would happen. TypeValidationErrorCallback(new TypeValidationError( call.Paren, $"Internal compiler error: no methods with name '{methodName}' could be found. This is a critical " + $"error that should have aborted the compilation before the {nameof(TypesResolvedValidator)} " + "validation is started. " )); } } else if (get.Methods.Length == 1) { MethodInfo method = get.Methods.Single(); var parameters = method.GetParameters(); // There is exactly one potential method to call in this case. We use this fact to provide better // error messages to the caller than when calling an overloaded method. if (parameters.Length != call.Arguments.Count) { TypeValidationErrorCallback(new TypeValidationError( call.Paren, $"Method '{methodName}' has {parameters.Length} parameter(s) but was called with {call.Arguments.Count} argument(s)" )); return; } for (int i = 0; i < call.Arguments.Count; i++) { ParameterInfo parameter = parameters[i]; Expr argument = call.Arguments[i]; if (!argument.TypeReference.IsResolved) { throw new PerlangInterpreterException( $"Internal compiler error: Argument '{argument}' to function {methodName} not resolved"); } // FIXME: call.Token is a bit off here; it would be useful when constructing compiler warnings based // on this if we could provide the token for the argument expression instead. However, the Expr type // as used by 'argument' is a non-token-based expression so this is currently impossible. // FIXME: `null` here has disadvantages as described elsewhere. if (!TypeCoercer.CanBeCoercedInto(parameter.ParameterType, argument.TypeReference.ClrType, null)) { // Very likely refers to a native method, where parameter names are not available at this point. TypeValidationErrorCallback(new TypeValidationError( argument.TypeReference.TypeSpecifier !, $"Cannot pass {argument.TypeReference.ClrType.ToTypeKeyword()} argument as {parameter.ParameterType.ToTypeKeyword()} parameter to {methodName}()")); } } } else { // Method is overloaded. Try to resolve the best match we can find. foreach (MethodInfo method in get.Methods) { var parameters = method.GetParameters(); if (parameters.Length != call.Arguments.Count) { // The number of parameters do not match, so this method will never be a suitable candidate // for our expression. continue; } bool coercionsFailed = false; for (int i = 0; i < call.Arguments.Count; i++) { ParameterInfo parameter = parameters[i]; Expr argument = call.Arguments[i]; if (!argument.TypeReference.IsResolved) { throw new PerlangInterpreterException( $"Internal compiler error: Argument '{argument}' to method {methodName} not resolved"); } // FIXME: The same caveat as above with call.Token applies here as well. if (!TypeCoercer.CanBeCoercedInto(parameter.ParameterType, argument.TypeReference.ClrType, null)) { coercionsFailed = true; break; } } if (!coercionsFailed) { // We have found a suitable overload to use. Update the expression get.Methods = ImmutableArray.Create(method); return; } } TypeValidationErrorCallback(new NameResolutionTypeValidationError( call.Paren, $"Method '{call.CalleeToString}' found, but no overload matches the provided parameters." )); } }
private void VisitCallExprForOtherCallee(Expr.Call expr) { Binding binding = GetVariableOrFunctionCallback(expr); if (binding == null) { TypeValidationErrorCallback( new NameResolutionTypeValidationError(expr.Paren, $"Attempting to call undefined function '{expr.CalleeToString}'") ); return; } IList <Parameter> parameters; string functionName; switch (binding) { case FunctionBinding functionBinding: Stmt.Function function = functionBinding.Function; if (function == null) { throw new NameResolutionTypeValidationError(expr.Paren, $"Internal compiler error: function for {expr} not expected to be null"); } parameters = function.Parameters; functionName = function.Name.Lexeme; break; default: throw new NameResolutionTypeValidationError(expr.Paren, $"Attempting to call invalid function {binding} using {expr}"); } if (parameters.Count != expr.Arguments.Count) { TypeValidationErrorCallback(new TypeValidationError( expr.Paren, $"Function '{functionName}' has {parameters.Count} parameter(s) but was called with {expr.Arguments.Count} argument(s)") ); return; } for (int i = 0; i < expr.Arguments.Count; i++) { Parameter parameter = parameters[i]; Expr argument = expr.Arguments[i]; if (!argument.TypeReference.IsResolved) { throw new PerlangInterpreterException($"Internal compiler error: Argument '{argument}' to function {functionName} not resolved"); } if (argument.TypeReference.IsNullObject) { compilerWarningCallback(new CompilerWarning($"Null parameter detected for '{parameter.Name.Lexeme}'", expr.TokenAwareCallee.Token, WarningType.NULL_USAGE)); } // FIXME: expr.Token is an approximation here as well (see other similar comments in this file) // FIXME: `null` here means that small-constants of e.g. `long` will not be able to be passed as `int` parameters. if (!TypeCoercer.CanBeCoercedInto(parameter.TypeReference, argument.TypeReference, null)) { if (parameter.Name != null) { TypeValidationErrorCallback(new TypeValidationError( argument.TypeReference.TypeSpecifier !, $"Cannot pass {argument.TypeReference.ClrType.ToTypeKeyword()} argument as parameter '{parameter.Name.Lexeme}: {parameter.TypeReference.ClrType.ToTypeKeyword()}' to {functionName}()")); } else { // Very likely refers to a native method, where parameter names are not available at this point. TypeValidationErrorCallback(new TypeValidationError( argument.TypeReference.TypeSpecifier !, $"Cannot pass {argument.TypeReference.ClrType.ToTypeKeyword()} argument as {parameter.TypeReference.ClrType.ToTypeKeyword()} parameter to {functionName}()")); } } } }