How to find insertions of any-wrap/c in TR code?

I'm helping a student that's using Racket in a compilers class, and they're running into an error where any-wrap/c is being applied to a cyclic value. The student is definitely intending to create a cyclic structure, so the question becomes: which part of the TR code is adding an any-wrap/c contract? My general understanding is that this wrapper will be applied when a value is passed to untyped racket code... but now I have to figure out which part of this code might be doing this. (The red error highlighting is somewhat unhelpful, here; an entire define is highlighted in red, and the red-arrow-chaining chains down not through application sites but instead through top-level-defines.)

Oh... Okay, I think I found the problem; I believe there's a use of cast that's causing the problem. This piece of test code reproduces what I believe to be the problem. I'm not saying this is a bug, but I am asking whether there's a good way to help students identify the problem (aside from the ever-useful DON'T USE CAST). And also... maybe this is a bug?

#lang typed/racket

(require typed/rackunit)

(struct foo ([a : Any]) #:transparent #:mutable)

;; create a cyclic 'foo' structure
(define (make-cyclic-structure)
  (define my-foo (foo 13))
  (set-foo-a! my-foo my-foo)
  my-foo)

;; pass a foo as an Any, then cast it and extract the field:
(define (take-any [x : Any]) : Any
  (define its-a-foo (cast x foo))
  (foo-a its-a-foo))

(take-any (make-cyclic-structure))

Yeah, if you change the (cast x foo) to (assert x foo?) instead it works.

So does using #lang typed/racket/shallow instead of changing the cast.

Well, I'm interested in typed/racket/shallow... but the real problem here is finding the problem in the first case. It's not clear to me how a student would get from the error message to any inkling that the problem involved a cast. Indeed, it could easily have happened without a cast, e.g. with a use of check-equal?. Maybe the right answer here is just a stack overflow post.

I do see that in general, though, students should be using assert whenever it's possible.

This PR seems to be responsible: any-wrap: avoid looping on cyclic values by bennn · Pull Request #824 · racket/typed-racket · GitHub

I don't think it's documented anywhere in the TR Reference that Any can't actually hold any value. Really should be. That would help people find the underlying issue.

1 Like

Many thanks for the pointer to Ben Greenman's PR. I think I would take issue with the statement that "Any can't actually hold any value", I think Any can hold any value. Safely allowing a value of type Any to be unwrapped, though, is currently not possible for all values. I suppose I can see your perspective.

is the neg-party part of the error message any help?

1 Like

The Any type can hold any value. However, you cannot pass values of type Any to arbitrary untyped programs (because Typed Racket) doesn't know how to protect them.

My advice would be (1) don't use cast (2) don't use Any.

Two solid and helpful pointers, many thanks.

Out of curiosity, has anything changed w.r.t. the expansion of match that would allow TR to better preserve the types of question mark patterns under a ... ? I got wedged at

#lang typed/racket

(define (f x) : (Listof Real)
  (match x
    [(list (? real? #{x : (Listof Real)}) ... 'p) x]))

... which does not type-check. I can solve this by ditching the inner hash-curly annotation and adding either a cast or an assert (sounds like I should stop recommending cast, and go to assert, though this will generally require a separate use of define-predicate, IIUC).

I have this macro in my codebase which I find myself using quite often:

(define-syntax-parse-rule (assert-type e:expr type:expr)
  (assert e (make-predicate type)))

In many naive situations, I could just replace cast with assert-type transparently.