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

Print argument of arbitrary type #320

Closed
PhucVH888 opened this issue Jan 21, 2016 · 8 comments · Fixed by #380
Closed

Print argument of arbitrary type #320

PhucVH888 opened this issue Jan 21, 2016 · 8 comments · Fixed by #380
Assignees

Comments

@PhucVH888
Copy link
Contributor

I got this error message when I defined a method to print an argument of arbitrary type.

encorec -c test.enc
Importing module String from /Users/vo/Dropbox/code/encore/bundles/standard/St$
encorec: Expr.hs: typeToPrintfFstr not defined for t

Here is the example code. At first, I intended to post the question, however, I found the simple solution later, so I am not sure if it is suitable to post it here.

class Foo<t>{
  def printF(x:t) : void
    print x
}

class Main{
  def main() : void {
    let foo = new Foo<int>
    in foo.printF(10)
  }
}

Solution: Instead of using print x directly, I slightly change to print (Just x) and then it works well. Just in case others can have the same problem to me in the future.

class Foo<t>{
  def printF(x:t) : void
    print (Just x)
}
@TobiasWrigstad
Copy link
Contributor

Your solution does not seem to be a good solution. Why should print x not work whereas print (Just x) works? I suggest fixing the root cause of this.

@PhucVH888
Copy link
Contributor Author

Yes, you're right, I have checked that and the printing results of print x and print (Just x) are completely different from expectation.

@EliasC
Copy link
Contributor

EliasC commented Jan 21, 2016

History repeats itself :) #46

I think printing a polymorphic value should not be allowed as it breaks the abstraction of parametricity. Also, we cannot be sure that the actual type is something that has a sensible string representation (it could be a closure for example) unless we have some kind of bounded polymorphism (something like class Foo<t : Showable> could be used to guarantee that t has a toString method).

@EliasC
Copy link
Contributor

EliasC commented Jan 21, 2016

To put an end to this, I suggest checking the arguments of print for polymorphic values to reject such programs.

@PhucVH888
Copy link
Contributor Author

I found the typeToPrintfFstr definition in Expr.hs but I don't quite understand how it works.

typeToPrintfFstr :: Ty.Type -> String
typeToPrintfFstr ty
    | Ty.isIntType ty          = "%lli"
    | Ty.isRealType ty         = "%f"
    | Ty.isStringObjectType ty = "%s"
    | Ty.isStringType ty       = "%s"
    | Ty.isCharType ty         = "%c"
    | Ty.isBoolType ty         = "bool<%zd>"
    | Ty.isRefType ty          = show ty ++ "<%p>"
    | Ty.isFutureType ty       = "fut<%p>"
    | otherwise = case translate ty of
                    Ptr something -> "%p"
                    _ -> error $ "Expr.hs: typeToPrintfFstr not defined for " ++ show ty

and in typechecker.hs

    --  count("{}", stringLit) = n
    --  E |- arg1 : t1 .. E |- argn : tn
    -- ---------------------------------------------
    --  E |- print(stringLit, arg1 .. argn) : void
    doTypecheck e@(Print {args = []}) = do
        let meta = emeta e
            eFormatString = setType stringType $ StringLiteral meta "\n"
        return $ setType voidType e {args = [eFormatString]}

    doTypecheck e@(Print {args = [arg]}) =
        do eArg <- doTypecheck arg
           let meta = emeta e
               eFormatString = setType stringType $ StringLiteral meta "{}\n"
           let newArgs = [eFormatString, eArg]
           return $ setType voidType e {args = newArgs}

    doTypecheck e@(Print {args}) =
        do eArgs <- mapM typecheck args
           let meta = emeta e
               fst = head eArgs
               rest = tail eArgs
               sugaredFst = fromJust $ getSugared fst
           unless (isStringLiteral sugaredFst) $
                  tcError $ "Formatted printing expects first argument '" ++
                            show (ppSugared fst) ++ "' to be a string literal"
           let formatString = stringLit sugaredFst
               noArgs = T.count (T.pack "{}") (T.pack formatString)
           unless (noArgs == length rest) $
                  tcError $ "Wrong number of arguments to format string. " ++
                            "Expected " ++ show noArgs ++ ", got " ++
                            show (length rest) ++ "."
           let eFormatString = setType stringType $
                               StringLiteral meta formatString
               newArgs = eFormatString : rest
           return $ setType voidType e {args = newArgs}

@EliasC
Copy link
Contributor

EliasC commented Jan 28, 2016

@PhucVH888: Your first snippet is part of the backend (that generates C-code from Encore code) and it finds the correct format specifier for the resulting printf call. For example, printing an print 42 is more or less translated into printf("%lli\n", 42).

Your second snippet is the type checking code for the Print expression. The three cases are for when there are no arguments, one argument, and more than one argument respectively. If you clarify which parts you don't understand, maybe I can help!

@TobiasWrigstad
Copy link
Contributor

@EliasC Did you implement this fix? What is the status on this?

@EliasC EliasC self-assigned this Apr 11, 2016
albertnetymk pushed a commit that referenced this issue Apr 18, 2016
This commit improves the printing support and fixes #352 and fixes #320.
The following list summarizes the current printing behavior:

* Reference types `T` are printed as `T@0x012345`, where the hexadecimal
  value is the address of the object. The same thing goes for futures,
  streams, arrays, ParTs and closures. `null` is printed as `T@0x0`.

* Values whose type isn't statically known cannot be printed. This
  includes any polymorphic values.

* Values of embedded types cannot be printed. You can (and should) drop
  down to C if you really need to print such a value (or closures or
  polymorphic values for that matter).

* Ranges are printed as `[1..10 by 1]`. The `by` part is always included
  for simplicity.

* Maybe types are printed like they should, although the generated code
  is a bit nasty. For each maybe type, a string is allocated for the
  sole purpose of being printed. The length of this string is
  approximated by summing approximations of the lengths of all
  subexpressions. Strings use their dynamic lengths and tuples the sum
  of the approximations of their contents. All other lengths are
  approximated as the length of their type plus 30 (compare with how
  reference types are printed). The 30 should be enough to hold any
  addresses, integers or reals. It would be better to calculate a
  maximum depending on the type, but if you're doing things like
  printing `Maybe`s, you probably don't care about performance anyway.

* Tuples are also printed like they should, and since their shape is
  known statically, the code isn't as bad.

* Values of `void` type are always printed as `()`

* Strings, chars, ints, booleans and reals are printed like you would
  expect. The only news here is proper printing of booleans as `true`
  and `false`.

The existing tests already test much of the printing, and some of them
have been updated to align with the new syntax. The test `printf` tests
some of the new printing.

Testing this has reminded me that the lack of proper inference for
`null` and `bottom` is annoying, but fixing this does not belong in this
PR.
@EliasC
Copy link
Contributor

EliasC commented Apr 21, 2016

Fixed (by disallowing printing of polymorphic values) by #380

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants